🧪 Problem-Driven Systems Lab

🐹 Caso 14 — Go 1.23#

⬅️ Caso 14 · ⚖️ Comparativa de los 7 stacks · 🐹 Perfil de Go · 🧬 Todos los perfiles

Stack Go del caso 14. Un pool que se achica en silencio contra uno con devolución garantizada.

El canal bufferizado es el pool#

go
type pool struct {
    free chan *conn      // lleva las conexiones Y limita cuántas hay en vuelo
}

<-pool.free adquiere, pool.free <- conn devuelve, y la capacidad del canal es el tamaño máximo. No hace falta semáforo aparte ni contador: una sola estructura hace las dos cosas.

El deadline se agrega envolviendo la recepción en un select:

go
select {
case c := <-p.free:
    return c, nil
case <-timer.C:
    return nil, errNoConn
}

Es la misma primitiva que el caso 04 usa para cancelación, el 08 para el bus de eventos y el 09 para la cuota. Cuatro problemas distintos, un solo concepto que aprender.

El límite honesto de Go en este caso#

defer es la garantía de devolución — y es una línea que hay que acordarse de escribir:

go
defer p.release(c)     // ← la línea que separa las dos variantes

Un return temprano antes del defer fuga la conexión y compila igual. Rust cierra esa puerta con Drop; Go la deja abierta y la hace fácil de grepear. Es una diferencia real, y es por lo que este caso es de los pocos donde Rust le gana a Go.

Primitivas nativas#

PrimitivaRol
chan *conn bufferizadoEl pool completo: contenedor y límite a la vez.
select + time.NewTimerEl deadline de adquisición.
deferLa devolución. Corre en todos los caminos de salida de la función.
sync/atomicContadores de acquired / released sin lock.
time.SleepEl tiempo de retención de la conexión.

Rutas#

RutaQué muestra
/healthliveness
/pool-leaky?requests=24&pool=4&query_ms=25&fail_rate=25leaked > 0 y hung creciente: el pool se vacía y no vuelve
/pool-managed?requests=24&pool=4&query_ms=25&fail_rate=25leaked = 0 y pool_available_after = pool_size
/pool/statetamaño, disponibles, adquiridas, devueltas y fugadas
/diagnostics/summaryacumulado por variante + ley de Little
/reset-labreconstruye el pool y limpia contadores

Parámetros: requests (1–200 llamadores), pool (1–64 conexiones), query_ms (1–500, cuánto retiene cada query), fail_rate (0–100 %, porcentaje de queries que lanzan).

Hub#

bash
docker compose -f compose.go.yml up -d --build
curl "http://127.0.0.1:8600/14/pool-leaky?requests=24&pool=4&query_ms=25&fail_rate=25"
curl "http://127.0.0.1:8600/14/pool-managed?requests=24&pool=4&query_ms=25&fail_rate=25"
curl "http://127.0.0.1:8600/14/pool/state"

Por qué acá el trabajo sí es un sleep#

En el caso 13 un sleep habría escondido el punto: lo que duele en una estampida es que el origen hace el trabajo N veces, así que hubo que quemar CPU de verdad.

Acá es al revés. Una conexión se retiene mientras se espera a la red, no mientras se calcula. Dormir es el modelo fiel del tiempo de retención; quemar CPU mediría otra cosa y además competiría con los propios hilos del laboratorio.

La misma decisión, tomada en sentidos opuestos, por la misma razón: modelar el recurso que realmente escasea.

Ver esta carpeta en GitHub ↗