Skip to content

Juannbel/tp0-base

 
 

Repository files navigation

TP0: Docker + Comunicaciones + Concurrencia

En el presente repositorio se provee un esqueleto básico de cliente/servidor, en donde todas las dependencias del mismo se encuentran encapsuladas en containers. Los alumnos deberán resolver una guía de ejercicios incrementales, teniendo en cuenta las condiciones de entrega descritas al final de este enunciado.

El cliente (Golang) y el servidor (Python) fueron desarrollados en diferentes lenguajes simplemente para mostrar cómo dos lenguajes de programación pueden convivir en el mismo proyecto con la ayuda de containers, en este caso utilizando Docker Compose.

Instrucciones de uso

El repositorio cuenta con un Makefile que incluye distintos comandos en forma de targets. Los targets se ejecutan mediante la invocación de: make <target>. Los target imprescindibles para iniciar y detener el sistema son docker-compose-up y docker-compose-down, siendo los restantes targets de utilidad para el proceso de depuración.

Los targets disponibles son:

target accion
docker-compose-up Inicializa el ambiente de desarrollo. Construye las imágenes del cliente y el servidor, inicializa los recursos a utilizar (volúmenes, redes, etc) e inicia los propios containers.
docker-compose-down Ejecuta docker-compose stop para detener los containers asociados al compose y luego docker-compose down para destruir todos los recursos asociados al proyecto que fueron inicializados. Se recomienda ejecutar este comando al finalizar cada ejecución para evitar que el disco de la máquina host se llene de versiones de desarrollo y recursos sin liberar.
docker-compose-logs Permite ver los logs actuales del proyecto. Acompañar con grep para lograr ver mensajes de una aplicación específica dentro del compose.
docker-image Construye las imágenes a ser utilizadas tanto en el servidor como en el cliente. Este target es utilizado por docker-compose-up, por lo cual se lo puede utilizar para probar nuevos cambios en las imágenes antes de arrancar el proyecto.
build Compila la aplicación cliente para ejecución en el host en lugar de en Docker. De este modo la compilación es mucho más veloz, pero requiere contar con todo el entorno de Golang y Python instalados en la máquina host.

Servidor

Se trata de un "echo server", en donde los mensajes recibidos por el cliente se responden inmediatamente y sin alterar.

Se ejecutan en bucle las siguientes etapas:

  1. Servidor acepta una nueva conexión.
  2. Servidor recibe mensaje del cliente y procede a responder el mismo.
  3. Servidor desconecta al cliente.
  4. Servidor retorna al paso 1.

Cliente

se conecta reiteradas veces al servidor y envía mensajes de la siguiente forma:

  1. Cliente se conecta al servidor.
  2. Cliente genera mensaje incremental.
  3. Cliente envía mensaje al servidor y espera mensaje de respuesta.
  4. Servidor responde al mensaje.
  5. Servidor desconecta al cliente.
  6. Cliente verifica si aún debe enviar un mensaje y si es así, vuelve al paso 2.

Ejemplo

Al ejecutar el comando make docker-compose-up y luego make docker-compose-logs, se observan los siguientes logs:

client1  | 2024-08-21 22:11:15 INFO     action: config | result: success | client_id: 1 | server_address: server:12345 | loop_amount: 5 | loop_period: 5s | log_level: DEBUG
client1  | 2024-08-21 22:11:15 INFO     action: receive_message | result: success | client_id: 1 | msg: [CLIENT 1] Message N°1
server   | 2024-08-21 22:11:14 DEBUG    action: config | result: success | port: 12345 | listen_backlog: 5 | logging_level: DEBUG
server   | 2024-08-21 22:11:14 INFO     action: accept_connections | result: in_progress
server   | 2024-08-21 22:11:15 INFO     action: accept_connections | result: success | ip: 172.25.125.3
server   | 2024-08-21 22:11:15 INFO     action: receive_message | result: success | ip: 172.25.125.3 | msg: [CLIENT 1] Message N°1
server   | 2024-08-21 22:11:15 INFO     action: accept_connections | result: in_progress
server   | 2024-08-21 22:11:20 INFO     action: accept_connections | result: success | ip: 172.25.125.3
server   | 2024-08-21 22:11:20 INFO     action: receive_message | result: success | ip: 172.25.125.3 | msg: [CLIENT 1] Message N°2
server   | 2024-08-21 22:11:20 INFO     action: accept_connections | result: in_progress
client1  | 2024-08-21 22:11:20 INFO     action: receive_message | result: success | client_id: 1 | msg: [CLIENT 1] Message N°2
server   | 2024-08-21 22:11:25 INFO     action: accept_connections | result: success | ip: 172.25.125.3
server   | 2024-08-21 22:11:25 INFO     action: receive_message | result: success | ip: 172.25.125.3 | msg: [CLIENT 1] Message N°3
client1  | 2024-08-21 22:11:25 INFO     action: receive_message | result: success | client_id: 1 | msg: [CLIENT 1] Message N°3
server   | 2024-08-21 22:11:25 INFO     action: accept_connections | result: in_progress
server   | 2024-08-21 22:11:30 INFO     action: accept_connections | result: success | ip: 172.25.125.3
server   | 2024-08-21 22:11:30 INFO     action: receive_message | result: success | ip: 172.25.125.3 | msg: [CLIENT 1] Message N°4
server   | 2024-08-21 22:11:30 INFO     action: accept_connections | result: in_progress
client1  | 2024-08-21 22:11:30 INFO     action: receive_message | result: success | client_id: 1 | msg: [CLIENT 1] Message N°4
server   | 2024-08-21 22:11:35 INFO     action: accept_connections | result: success | ip: 172.25.125.3
server   | 2024-08-21 22:11:35 INFO     action: receive_message | result: success | ip: 172.25.125.3 | msg: [CLIENT 1] Message N°5
client1  | 2024-08-21 22:11:35 INFO     action: receive_message | result: success | client_id: 1 | msg: [CLIENT 1] Message N°5
server   | 2024-08-21 22:11:35 INFO     action: accept_connections | result: in_progress
client1  | 2024-08-21 22:11:40 INFO     action: loop_finished | result: success | client_id: 1
client1 exited with code 0

Parte 1: Introducción a Docker

En esta primera parte del trabajo práctico se plantean una serie de ejercicios que sirven para introducir las herramientas básicas de Docker que se utilizarán a lo largo de la materia. El entendimiento de las mismas será crucial para el desarrollo de los próximos TPs.

Ejercicio N°1:

Definir un script de bash generar-compose.sh que permita crear una definición de Docker Compose con una cantidad configurable de clientes. El nombre de los containers deberá seguir el formato propuesto: client1, client2, client3, etc.

El script deberá ubicarse en la raíz del proyecto y recibirá por parámetro el nombre del archivo de salida y la cantidad de clientes esperados:

./generar-compose.sh docker-compose-dev.yaml 5

Considerar que en el contenido del script pueden invocar un subscript de Go o Python:

#!/bin/bash
echo "Nombre del archivo de salida: $1"
echo "Cantidad de clientes: $2"
python3 mi-generador.py $1 $2

En el archivo de Docker Compose de salida se pueden definir volúmenes, variables de entorno y redes con libertad, pero recordar actualizar este script cuando se modifiquen tales definiciones en los sucesivos ejercicios.

Ejercicio N°2:

Modificar el cliente y el servidor para lograr que realizar cambios en el archivo de configuración no requiera reconstruír las imágenes de Docker para que los mismos sean efectivos. La configuración a través del archivo correspondiente (config.ini y config.yaml, dependiendo de la aplicación) debe ser inyectada en el container y persistida por fuera de la imagen (hint: docker volumes).

Ejercicio N°3:

Crear un script de bash validar-echo-server.sh que permita verificar el correcto funcionamiento del servidor utilizando el comando netcat para interactuar con el mismo. Dado que el servidor es un echo server, se debe enviar un mensaje al servidor y esperar recibir el mismo mensaje enviado.

En caso de que la validación sea exitosa imprimir: action: test_echo_server | result: success, de lo contrario imprimir:action: test_echo_server | result: fail.

El script deberá ubicarse en la raíz del proyecto. Netcat no debe ser instalado en la máquina host y no se pueden exponer puertos del servidor para realizar la comunicación (hint: docker network). `

Ejercicio N°4:

Modificar servidor y cliente para que ambos sistemas terminen de forma graceful al recibir la signal SIGTERM. Terminar la aplicación de forma graceful implica que todos los file descriptors (entre los que se encuentran archivos, sockets, threads y procesos) deben cerrarse correctamente antes que el thread de la aplicación principal muera. Loguear mensajes en el cierre de cada recurso (hint: Verificar que hace el flag -t utilizado en el comando docker compose down).

Parte 2: Repaso de Comunicaciones

Las secciones de repaso del trabajo práctico plantean un caso de uso denominado Lotería Nacional. Para la resolución de las mismas deberá utilizarse como base el código fuente provisto en la primera parte, con las modificaciones agregadas en el ejercicio 4.

Ejercicio N°5:

Modificar la lógica de negocio tanto de los clientes como del servidor para nuestro nuevo caso de uso.

Cliente

Emulará a una agencia de quiniela que participa del proyecto. Existen 5 agencias. Deberán recibir como variables de entorno los campos que representan la apuesta de una persona: nombre, apellido, DNI, nacimiento, numero apostado (en adelante 'número'). Ej.: NOMBRE=Santiago Lionel, APELLIDO=Lorca, DOCUMENTO=30904465, NACIMIENTO=1999-03-17 y NUMERO=7574 respectivamente.

Los campos deben enviarse al servidor para dejar registro de la apuesta. Al recibir la confirmación del servidor se debe imprimir por log: action: apuesta_enviada | result: success | dni: ${DNI} | numero: ${NUMERO}.

Servidor

Emulará a la central de Lotería Nacional. Deberá recibir los campos de la cada apuesta desde los clientes y almacenar la información mediante la función store_bet(...) para control futuro de ganadores. La función store_bet(...) es provista por la cátedra y no podrá ser modificada por el alumno. Al persistir se debe imprimir por log: action: apuesta_almacenada | result: success | dni: ${DNI} | numero: ${NUMERO}.

Comunicación:

Se deberá implementar un módulo de comunicación entre el cliente y el servidor donde se maneje el envío y la recepción de los paquetes, el cual se espera que contemple:

  • Definición de un protocolo para el envío de los mensajes.
  • Serialización de los datos.
  • Correcta separación de responsabilidades entre modelo de dominio y capa de comunicación.
  • Correcto empleo de sockets, incluyendo manejo de errores y evitando los fenómenos conocidos como short read y short write.

Ejercicio N°6:

Modificar los clientes para que envíen varias apuestas a la vez (modalidad conocida como procesamiento por chunks o batchs). Los batchs permiten que el cliente registre varias apuestas en una misma consulta, acortando tiempos de transmisión y procesamiento.

La información de cada agencia será simulada por la ingesta de su archivo numerado correspondiente, provisto por la cátedra dentro de .data/datasets.zip. Los archivos deberán ser inyectados en los containers correspondientes y persistido por fuera de la imagen (hint: docker volumes), manteniendo la convencion de que el cliente N utilizara el archivo de apuestas .data/agency-{N}.csv .

En el servidor, si todas las apuestas del batch fueron procesadas correctamente, imprimir por log: action: apuesta_recibida | result: success | cantidad: ${CANTIDAD_DE_APUESTAS}. En caso de detectar un error con alguna de las apuestas, debe responder con un código de error a elección e imprimir: action: apuesta_recibida | result: fail | cantidad: ${CANTIDAD_DE_APUESTAS}.

La cantidad máxima de apuestas dentro de cada batch debe ser configurable desde config.yaml. Respetar la clave batch: maxAmount, pero modificar el valor por defecto de modo tal que los paquetes no excedan los 8kB.

Por su parte, el servidor deberá responder con éxito solamente si todas las apuestas del batch fueron procesadas correctamente.

Ejercicio N°7:

Modificar los clientes para que notifiquen al servidor al finalizar con el envío de todas las apuestas y así proceder con el sorteo. Inmediatamente después de la notificacion, los clientes consultarán la lista de ganadores del sorteo correspondientes a su agencia. Una vez el cliente obtenga los resultados, deberá imprimir por log: action: consulta_ganadores | result: success | cant_ganadores: ${CANT}.

El servidor deberá esperar la notificación de las 5 agencias para considerar que se realizó el sorteo e imprimir por log: action: sorteo | result: success. Luego de este evento, podrá verificar cada apuesta con las funciones load_bets(...) y has_won(...) y retornar los DNI de los ganadores de la agencia en cuestión. Antes del sorteo no se podrán responder consultas por la lista de ganadores con información parcial.

Las funciones load_bets(...) y has_won(...) son provistas por la cátedra y no podrán ser modificadas por el alumno.

No es correcto realizar un broadcast de todos los ganadores hacia todas las agencias, se espera que se informen los DNIs ganadores que correspondan a cada una de ellas.

Parte 3: Repaso de Concurrencia

En este ejercicio es importante considerar los mecanismos de sincronización a utilizar para el correcto funcionamiento de la persistencia.

Ejercicio N°8:

Modificar el servidor para que permita aceptar conexiones y procesar mensajes en paralelo. En caso de que el alumno implemente el servidor en Python utilizando multithreading, deberán tenerse en cuenta las limitaciones propias del lenguaje.

Condiciones de Entrega

Se espera que los alumnos realicen un fork del presente repositorio para el desarrollo de los ejercicios y que aprovechen el esqueleto provisto tanto (o tan poco) como consideren necesario.

Cada ejercicio deberá resolverse en una rama independiente con nombres siguiendo el formato ej${Nro de ejercicio}. Se permite agregar commits en cualquier órden, así como crear una rama a partir de otra, pero al momento de la entrega deberán existir 8 ramas llamadas: ej1, ej2, ..., ej7, ej8. (hint: verificar listado de ramas y últimos commits con git ls-remote)

Se espera que se redacte una sección del README en donde se indique cómo ejecutar cada ejercicio y se detallen los aspectos más importantes de la solución provista, como ser el protocolo de comunicación implementado (Parte 2) y los mecanismos de sincronización utilizados (Parte 3).

Se proveen pruebas automáticas de caja negra. Se exige que la resolución de los ejercicios pase tales pruebas, o en su defecto que las discrepancias sean justificadas y discutidas con los docentes antes del día de la entrega. El incumplimiento de las pruebas es condición de desaprobación, pero su cumplimiento no es suficiente para la aprobación. Respetar las entradas de log planteadas en los ejercicios, pues son las que se chequean en cada uno de los tests.

La corrección personal tendrá en cuenta la calidad del código entregado y casos de error posibles, se manifiesten o no durante la ejecución del trabajo práctico. Se pide a los alumnos leer atentamente y tener en cuenta los criterios de corrección informados en el campus.

Ejecución y explicación resumida de soluciones

Ejercicio 1

Para este ejercicio agregué el script generar-compose.sh, el mismo genera un archivo de Docker Compose con una cantidad de clientes configurable. Decidí mantenerlo simple, con todo lo necesario en el mismo script de bash, el mismo genera el archivo necesario si es que no existe, o lo pisa si ya existía, agrega lo referente al servicio del server, y luego por cada cliente pedido, un servicio de cliente con su respectivo id, finalmente se agrega la definición de la red para conectarlos.

Para ejecutar este script se debe llamar de la siguiente manera:

./generar-compose.sh <nombre-archivo-salida> <cantidad-cliente>

En caso de necesitar cambiar variables de entorno, volumenes, etc, simplemente se puede editar la definición que se usa para los clientes y volver a correr el script, generando un nuevo Docker Compose.

Ejercicio 2

Para que no sea necesario reconstruir las imagenes de Docker al hacer cambios en la configuración, empecé por hacer un bind mount de los mismos en la ubicación en que se espera, de esta forma, el contenedor siempre está actualizado con la última versión del archivo sin necesidad de volver a hacer un build. Luego de eso, agregué ambos archivos de configuración en el .dockerignore, para que al momento de correr un contenedor, docker ignore los cambios sobre esos archivos, que de otra forma llevarian a hacer un nuevo build del contenedor.

Ejercicio 3

Para realizar la validación del servidor con el script validar-echo-server.sh usando netcat, sin necesidad de ser instalado en la máquina host, lo que hice fue correr un contenedor temporal, que se borra luego de usado (docker run --rm). El mismo lo conecto a la red que se genera con el Docker Compose (tp0_testing_net), con lo cual no hace falta exponer ningun puerto. La imagen que elegí usar en este caso es una de las mas livianas que encontré que contaba con netcat, busybox. Lo único que hago es entonces enviar un mensaje al servidor (echo $MENSAJE | nc $ADDR_SERVIDOR), y verifico que la salida sea igual al mensaje enviado, indicando que funciona correctamente, en cualquier otro caso, muestra que el test falló.

Ejercicio 4

Para que tanto el cliente como el servidor cierren de forma graceful al recibir la señal de SIGTERM configuré en ambos casos un handler para la misma.

  • En el caso del cliente (escrito en Go), el handler se ejecuta en una goroutine, que llama al método Stop del objeto cliente, en el mismo, se setea un flag interno para indicar que debe frenar, y se cierra un canal interno, la razón de usar este último es para manejar el caso en el que el stop llega en el momento que el cliente esta en el sleep, el cambio para lograr que reaccioné fue utilizar un select, que "espera" en dos canales, pudiendo salir por dos razones; o bien pasó el tiempo configurado, o se cerró el canal interno que indica que hay que parar. Una vez que sale del loop se realiza el cleanup cerrando la conexión con el servidor.
  • En el caso del servidor (escrito en python), utilizando la librería signal configuro un handler para SIGTERM, que llama a un metodo stop del server. Parecido al cliente, se setea un flag para detener el loop, y además, se cierra el "welcoming socket" (socket destinado a aceptar las conexiones), de esta forma evitamos que el servidor quede "colgado" aceptando una conexión. Con esto es suficiente pues los demás sockets que se van abriendo para comunicarse con los clientes siempre son cerrados en el finally del metodo __handle_client_connection.

Ejercicio 5

Para este ejercicio, implementé un protocolo de comunicación sencillo que permita enviar una apuesta del lado del cliente al servidor, y recibir una confirmación.

En el mismo, se envía un string que contiene la apuesta serializada de la siguiente forma: <agencia>|<nombre>|<apellido>|<documento>|<nacimiento>|<numero>

Al inicio del mismo se agrega un byte indicando la longitud total del string (esto agrega la limitación de que el largo de la apuesta serializada no puede exceder los 255 caracteres, lo cual me parece razonable para los datos que se utilizan). Del lado del servidor entonces se empieza por leer un byte, y luego realiza una segunda lectura del socket con la longitud obtenida del primer byte.

Si se recibió la apuesta correctamente, se envía un byte en 1 como confirmación, al recibir este byte el cliente sabe que la apuesta fue procesada correctamente y puede mostrar el log.

Para evitar short reads y short writes implementé la clase Socket de ambos lados, usada por el protocolo, que se encarga de manejar las operaciones de lectura y escritura, llamando todas las veces que sea necesario a recv y send para completarlas. En el caso del servidor escrito en python, no hizo falta implementar la lógica para el send, ya que el socket primitivo provee el método sendall que nos asegura que se enviará todo el buffer o lanzará un error. A continuación dejo la fracción de código correspondiente al método recvall implementado, los métodos del socket implementados en go tienen la misma lógica.

def recvall(self, length):
    data = b''
    while len(data) < length:
        packet = self._sock.recv(length - len(data))
        if not packet:
            break
        data += packet
        
    return data

Ejercicio 6

Para la implementación de este ejercicio partí del protocolo anterior, pero extendiéndolo para poder enviar múltiples apuestas por mensaje en forma de batch. Lo que hago en este caso es serializar todas las apuestas del mismo batch con la siguiente forma: <apuesta1>#<apuesta2>#...#<apuestaN>

La serializacion de cada apuesta la mantuve igual que el ejercicio anterior: <agencia>|<nombre>|<apellido>|<documento>|<nacimiento>|<numero>

Ahora, para que el servidor pueda saber cuánto leer del socket, en el protocolo envío primero dos bytes (en big-endian) que indican el tamaño total del batch de apuestas (serializado, incluyendo los separadores) como un unsigned short. En este caso decidí utilizar dos bytes ya que me da un rango de hasta 65535, como se aclara en la consigna, el batch completo serializado no puede superar los 8kB (8192 bytes).

El servidor empieza por recibir los dos bytes que indican el tamaño del batch serializado, luego recibe el batch completo en una sola lectura, y lo deserializa para obtener las apuestas. Si no ocurre ningun error, se imprime el log con la cantidad de apuestas recibidas en ese batch, se envía una confirmación (enviando un byte en 1), y se espera el siguiente batch del cliente. De ocurrir algún error al momento de deserializar el batch, se envía un código de error (un byte en 2), y se cierra la conexión con ese cliente.

El proceso de recibir batchs se repite hasta que al momento del leer el largo del batch se obtiene un 0 (unsigned short con los dos bytes en 0), indicando que no quedan mas apuestas. En este punto se cierra la conexión con el cliente y sigue esperando al siguiente.

Ejercicio 7

Para este ejercicio reutilicé gran parte del ejercicio anterior, como la parte de enviar apuestas por batches de mantiene, toda esa parte del protocolo también. Los agregados en este caso son:

  • Nuevos códigos de operación/acción
  • Protocolo para enviar ganadores

Nuevos codigos de operación/acción

Al conectarse un cliente al servidor, este debe empezar por enviar un código de 1 byte indicando que acción quiere realizar, ya sea enviar apuestas o consultar los ganadores de su agencia.

En el primer caso, el servidor recibe el código (SENDING_BETS) y espera los sucesivos batches de apuestas, de la misma forma que lo hacia en el ejercicio anterior (largo del batch codificado, batch codificado, enviar confirmación (BATCH_RECEIVED), largo del batch codificado, batch codificado, etc), finalizando cuando el cliente envía un 0 como tamaño del batch, dando por finalizada la comunicación.

En el segundo caso, luego del código que indica que se quieren consultar los ganadores (REQUEST_RESULTS), viene el numero de cliente/agencia que está consultando expresado en 1 byte (para que el servidor pueda enviarle los ganadores correspondientes). Al recibirlo, si el sorteo ya fue realizado, el servidor empieza enviando un código de un byte que indica que a continuación le enviará los ganadores (SENDING_RESULTS), y procede a enviar los ganadores con el protocolo que se explica a continuación. Si el sorteo aún no fue realizado, se envía un código de un byte indicando esto (RESULTS_NOT_READY), y se cierra la conexión para poder seguir atendiendo a otras agencias para lograr juntar todas las apuestas. En este segundo caso el cliente esperará 1 segundo y volverá a intentar consultar, dando tiempo a los demás para que envíen las apuestas.

Protocolo para enviar ganadores

El protocolo para enviar ganadores tiene la misma forma que el protocolo para enviar apuestas, solo que en este caso todos estarán contenidos en un mensaje. El mensaje queda serializado de la siguiente forma: <dni_ganador1>$<dni_ganador2>$...$<dni_ganadorN>

El servidor primero incluirá un uint16 (en big-endian) que indica el tamaño total del mensaje serializado, y luego el mensaje. Con esto el cliente puede empezar por leer el uint16 y luego el mensaje completo, y haciendo uso de un split obtiene sus ganadores.

Ejercicio 8

Este ejercicio introduce pocos cambios respecto al ejercicio anterior, buscando que el servidor pueda procesar mensajes de distintos clientes en paralelo. Para esto utilicé la clase Thread de la librería threading, lanzando un thread por cada cliente que se contecta.

Como se aclara en la consigna, debido al GIL (global interpreter lock), que protege el acceso a los objetos de python, solo un thread puede ejecutar código python al mismo tiempo, afectando fuertemente la performance de programas cpu-intensive que busquen hacer uso del paralelismo. Sin embargo, el GIL se libera al momento de realizar operaciones I/O (como lectura/escritura de archivos/sockets), por lo que para este caso de uso nos resulta beneficioso, cuando un hilo se bloquea leyendo de su socket, otro puede continuar con su ejecución, lo mismo pasa cuando se obtienen las apuestas y van a escribirse en el archivo, ese hilo libera el GIL permitiendo que otro hilo continúe su ejecución.

Para manejar la concurrencia debido a que las funciones de load_bets() y store_bets() no son thread-safe, utilicé un Lock de la misma librería threading, compartido entre todos los hilos, para llamar a cualquiera de esas funciones, acceder a los resultados o la cantidad de agencias procesadas, es necesario adquirir el lock, de esta forma evitamos cualquier tipo de race condition.

Como en todos los casos buscamos tener el lock tomado por la menor cantidad de tiempo posible, al momento de obtener los ganadores, se guarda una copia de la lista de ganadores de esa agencia y se libera, para evitar mantener el lock tomado durante toda la operación de enviar los ganadores al cliente.

Finalmente, otra de las partes importantes de la implementación, es que guardamos por cada conexión (y por consecuente, thread que se lanza), una referencia a ese thread y su protocolo (que contiene el socket), por lo que en el caso de una señal SIGTERM podemos iterar sobre estos, cerrar los sockets (desbloqueando así los hilos) y joinearlos, teniendo así un graceful shutdown, liberando todos los recursos. Además, luego de que el hilo principal acepta una conexión (lanzando un thread), recorre estos hilos y verificando si aun están ejecución, o ya pueden ser joineados, liberando los recursos lo antes posible.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • Python 46.4%
  • Go 42.6%
  • Shell 4.0%
  • Makefile 3.8%
  • Dockerfile 3.2%