[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"article-deep-evm-29-semaforos-rust-asincrono-deadlocks-fire-forget":3},{"article":4,"author":51},{"id":5,"category_id":6,"title":7,"slug":8,"excerpt":9,"content_md":10,"content_html":11,"locale":12,"author_id":13,"published":14,"published_at":15,"meta_title":16,"meta_description":17,"focus_keyword":18,"og_image":19,"canonical_url":19,"robots_meta":20,"created_at":15,"updated_at":15,"tags":21,"category_name":31,"related_articles":32},"d8000000-0000-0000-0000-000000000301","a0000000-0000-0000-0000-000000000086","Deep EVM #29: Semáforos en Rust Asíncrono — Caza de Deadlocks y Patrones Fire-and-Forget","deep-evm-29-semaforos-rust-asincrono-deadlocks-fire-forget","Una inmersión profunda en tokio::sync::Semaphore para control de contrapresión, patrones de escritura fire-and-forget, diagnóstico de deadlocks con tracing y tokio-console, y soluciones endurecidas para producción usando permisos RAII y timeouts de adquisición.","## Por qué semáforos en Rust asíncrono\n\nCuando ejecutas un pipeline de alto rendimiento — un bot de MEV procesando 180.000 cadenas de arbitraje por bloque, un servidor API manejando 10.000 solicitudes concurrentes, o un job ETL escribiendo millones de filas — inevitablemente llegas a un techo de recursos. Los pools de conexiones a base de datos se agotan. Los proveedores de RPC te limitan. La memoria se dispara porque lanzaste 50.000 tareas tokio, cada una con datos de una cadena.\n\nEl enfoque ingenuo es concurrencia ilimitada: `tokio::spawn` para cada unidad de trabajo y esperar que el runtime lo resuelva. No lo hace. En producción, observamos 15,4 GB de uso de memoria de 2,7 millones de tareas spawneadas, cada una con un `Vec\u003CHop>` más contexto de simulación. La solución fue concurrencia por lotes con contrapresión basada en semáforos, que redujo la memoria a 0,8 GB.\n\nUn semáforo es la primitiva correcta cuando necesitas limitar el número de operaciones concurrentes sin serializarlas completamente. A diferencia de un mutex (que permite exactamente una), un semáforo permite N accesos concurrentes. Esto lo hace perfecto para:\n\n- **Concurrencia de escritura a BD**: limitar al tamaño del pool de conexiones (ej. 20 escrituras concurrentes)\n- **Limitación de velocidad RPC**: respetar los límites de tasa de los proveedores (ej. 100 solicitudes\u002Fsegundo)\n- **Control de memoria**: prevenir que las tareas spawneadas se acumulen sin límite\n- **Control de backpressure**: propagar presión de retorno desde los recursos downstream\n\n## La API de tokio::sync::Semaphore\n\nLa API del semáforo de tokio es mínima pero poderosa:\n\n```rust\nuse tokio::sync::Semaphore;\nuse std::sync::Arc;\n\n\u002F\u002F Crear un semáforo con N permisos\nlet sem = Arc::new(Semaphore::new(20));\n\n\u002F\u002F Adquirir un permiso (espera si todos están ocupados)\nlet permit = sem.acquire().await.unwrap();\n\u002F\u002F ... hacer trabajo ...\ndrop(permit); \u002F\u002F Liberar el permiso\n\n\u002F\u002F Intentar adquirir sin esperar\nlet permit = sem.try_acquire();\n\n\u002F\u002F Adquirir con timeout\nlet permit = tokio::time::timeout(\n    Duration::from_secs(5),\n    sem.acquire()\n).await;\n```\n\n## Patrón: escritura fire-and-forget con semáforo\n\nUn patrón común en bots de MEV es la escritura fire-and-forget: enviar resultados a la base de datos sin esperar confirmación, pero con contrapresión para no desbordar el pool de conexiones.\n\n```rust\nstruct Writer {\n    pool: PgPool,\n    semaphore: Arc\u003CSemaphore>,\n}\n\nimpl Writer {\n    fn new(pool: PgPool, max_concurrent: usize) -> Self {\n        Self {\n            pool,\n            semaphore: Arc::new(Semaphore::new(max_concurrent)),\n        }\n    }\n    \n    fn write(&self, data: WritePayload) {\n        let pool = self.pool.clone();\n        let sem = self.semaphore.clone();\n        \n        tokio::spawn(async move {\n            \u002F\u002F Esperar por un permiso (backpressure aquí)\n            let _permit = match tokio::time::timeout(\n                Duration::from_secs(10),\n                sem.acquire()\n            ).await {\n                Ok(Ok(permit)) => permit,\n                _ => {\n                    tracing::warn!(\"Write semaphore timeout, dropping payload\");\n                    return;\n                }\n            };\n            \n            \u002F\u002F Ejecutar la escritura\n            if let Err(e) = insert_data(&pool, &data).await {\n                tracing::error!(\"Write failed: {}\", e);\n            }\n            \u002F\u002F _permit se libera aquí (RAII)\n        });\n    }\n}\n```\n\nEste patrón es no-bloqueante para el caller — la escritura se ejecuta en una tarea separada. Pero el semáforo previene que se acumulen más escrituras de las que el pool puede manejar.\n\n## Diagnóstico de deadlocks\n\nLos deadlocks con semáforos ocurren cuando:\n\n1. **Adquisición anidada**: una tarea que ya tiene un permiso intenta adquirir otro del mismo semáforo\n2. **Dependencia circular**: tarea A espera permiso de semáforo B, tarea B espera permiso de semáforo A\n3. **Permiso retenido a través de await**: una tarea adquiere un permiso, hace un await que nunca resuelve, y nunca libera el permiso\n\n### Detección con tokio-console\n\n```rust\n\u002F\u002F En main.rs\nconsole_subscriber::init();\n\n\u002F\u002F Ejecutar con:\n\u002F\u002F RUSTFLAGS=\"--cfg tokio_unstable\" cargo run\n\u002F\u002F tokio-console\n```\n\ntokio-console muestra:\n- Tareas bloqueadas esperando semáforos\n- Duración del bloqueo\n- Stack traces de las tareas bloqueadas\n\n### Detección con tracing\n\n```rust\nuse tracing::{instrument, warn};\n\n#[instrument(skip(sem), fields(available = sem.available_permits()))]\nasync fn acquire_with_diagnostics(\n    sem: &Semaphore,\n    label: &str,\n) -> Result\u003CSemaphorePermit\u003C'_>, AcquireError> {\n    let start = Instant::now();\n    let permit = sem.acquire().await?;\n    let elapsed = start.elapsed();\n    \n    if elapsed > Duration::from_secs(1) {\n        warn!(\n            label = label,\n            elapsed_ms = elapsed.as_millis(),\n            \"Slow semaphore acquisition detected\"\n        );\n    }\n    \n    Ok(permit)\n}\n```\n\n## Patrón: semáforo con timeout obligatorio\n\nEn producción, nunca adquieras un semáforo sin timeout:\n\n```rust\nasync fn safe_acquire(\n    sem: &Semaphore,\n    timeout_secs: u64,\n) -> Result\u003CSemaphorePermit\u003C'_>> {\n    tokio::time::timeout(\n        Duration::from_secs(timeout_secs),\n        sem.acquire()\n    )\n    .await\n    .map_err(|_| anyhow!(\"Semaphore acquire timed out after {}s\", timeout_secs))?\n    .map_err(|e| anyhow!(\"Semaphore closed: {}\", e))\n}\n```\n\n## Permisos RAII: nunca perder un permiso\n\nEl patrón RAII (Resource Acquisition Is Initialization) de Rust garantiza que los permisos se liberen incluso si la tarea paniquea:\n\n```rust\nasync fn process_with_permit(sem: Arc\u003CSemaphore>, data: Data) {\n    let _permit = sem.acquire().await.unwrap();\n    \u002F\u002F Si process() paniquea, _permit aún se libera\n    \u002F\u002F porque Rust ejecuta drop() al salir del scope\n    process(data).await;\n}\n```\n\nEsto es fundamentalmente más seguro que el patrón acquire\u002Frelease manual de otros lenguajes, donde un panic entre acquire y release pierde el permiso permanentemente.\n\n## Patrones avanzados\n\n### Semáforo ponderado\n\nPara operaciones con diferente peso de recurso:\n\n```rust\nlet sem = Semaphore::new(100); \u002F\u002F 100 unidades de capacidad\n\n\u002F\u002F Operación pequeña: 1 unidad\nlet _permit = sem.acquire_many(1).await?;\n\n\u002F\u002F Operación grande: 10 unidades\nlet _permit = sem.acquire_many(10).await?;\n```\n\n### Rate limiter basado en semáforo\n\n```rust\nstruct RateLimiter {\n    semaphore: Arc\u003CSemaphore>,\n}\n\nimpl RateLimiter {\n    fn new(max_per_second: usize) -> Self {\n        let sem = Arc::new(Semaphore::new(max_per_second));\n        let sem_clone = sem.clone();\n        \n        \u002F\u002F Reponer permisos cada segundo\n        tokio::spawn(async move {\n            loop {\n                tokio::time::sleep(Duration::from_secs(1)).await;\n                let deficit = max_per_second - sem_clone.available_permits();\n                sem_clone.add_permits(deficit);\n            }\n        });\n        \n        Self { semaphore: sem }\n    }\n    \n    async fn acquire(&self) -> SemaphorePermit\u003C'_> {\n        self.semaphore.acquire().await.unwrap()\n    }\n}\n```\n\n## Métricas de producción\n\nMonitorea estos indicadores:\n\n- **available_permits**: si está constantemente en 0, necesitas más capacidad\n- **acquire_duration_p99**: latencia de adquisición en el percentil 99\n- **timeout_count**: número de timeouts de adquisición por minuto\n- **held_duration_p99**: cuánto tiempo se retienen los permisos\n\n```rust\nuse prometheus::{Histogram, IntGauge};\n\nlet permits_available = IntGauge::new(\n    \"semaphore_permits_available\",\n    \"Number of available semaphore permits\"\n).unwrap();\n\nlet acquire_duration = Histogram::new(\n    \"semaphore_acquire_duration_seconds\",\n    \"Time to acquire a semaphore permit\"\n).unwrap();\n```\n\n## Caso de estudio: bot de MEV\n\nNuestro bot de MEV usa tres semáforos:\n\n1. **RPC semaphore (50 permisos)**: limita llamadas concurrentes al nodo Ethereum\n2. **Simulation semaphore (20 permisos)**: limita simulaciones EVM concurrentes (intensivas en CPU)\n3. **DB write semaphore (10 permisos)**: limita escrituras concurrentes al pool PostgreSQL\n\n```rust\nstruct MevBot {\n    rpc_sem: Arc\u003CSemaphore>,\n    sim_sem: Arc\u003CSemaphore>,\n    db_sem: Arc\u003CSemaphore>,\n}\n\nimpl MevBot {\n    async fn process_opportunity(&self, opp: Opportunity) {\n        \u002F\u002F Fase 1: leer estado on-chain\n        let _rpc = self.rpc_sem.acquire().await.unwrap();\n        let state = self.read_state(&opp).await;\n        drop(_rpc);\n        \n        \u002F\u002F Fase 2: simular\n        let _sim = self.sim_sem.acquire().await.unwrap();\n        let result = self.simulate(&opp, state).await;\n        drop(_sim);\n        \n        \u002F\u002F Fase 3: escribir resultado (fire-and-forget)\n        if result.is_profitable() {\n            let db_sem = self.db_sem.clone();\n            tokio::spawn(async move {\n                let _db = db_sem.acquire().await.unwrap();\n                save_result(result).await;\n            });\n        }\n    }\n}\n```\n\nCada fase libera su permiso antes de que la siguiente lo adquiera, maximizando el paralelismo entre fases.\n\n## Conclusión\n\nLos semáforos son la primitiva de concurrencia esencial para sistemas de alto rendimiento en Rust asíncrono. Proporcionan control de backpressure sin serialización completa, previenen la exhaustión de recursos, y con el patrón RAII de Rust, garantizan la liberación de permisos incluso ante errores. Siempre usa timeouts de adquisición en producción, monitorea los permisos disponibles, y diagnostica los bloqueos con tokio-console y tracing instrumentado.","\u003Ch2 id=\"por-qu-sem-foros-en-rust-as-ncrono\">Por qué semáforos en Rust asíncrono\u003C\u002Fh2>\n\u003Cp>Cuando ejecutas un pipeline de alto rendimiento — un bot de MEV procesando 180.000 cadenas de arbitraje por bloque, un servidor API manejando 10.000 solicitudes concurrentes, o un job ETL escribiendo millones de filas — inevitablemente llegas a un techo de recursos. Los pools de conexiones a base de datos se agotan. Los proveedores de RPC te limitan. La memoria se dispara porque lanzaste 50.000 tareas tokio, cada una con datos de una cadena.\u003C\u002Fp>\n\u003Cp>El enfoque ingenuo es concurrencia ilimitada: \u003Ccode>tokio::spawn\u003C\u002Fcode> para cada unidad de trabajo y esperar que el runtime lo resuelva. No lo hace. En producción, observamos 15,4 GB de uso de memoria de 2,7 millones de tareas spawneadas, cada una con un \u003Ccode>Vec&lt;Hop&gt;\u003C\u002Fcode> más contexto de simulación. La solución fue concurrencia por lotes con contrapresión basada en semáforos, que redujo la memoria a 0,8 GB.\u003C\u002Fp>\n\u003Cp>Un semáforo es la primitiva correcta cuando necesitas limitar el número de operaciones concurrentes sin serializarlas completamente. A diferencia de un mutex (que permite exactamente una), un semáforo permite N accesos concurrentes. Esto lo hace perfecto para:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Cstrong>Concurrencia de escritura a BD\u003C\u002Fstrong>: limitar al tamaño del pool de conexiones (ej. 20 escrituras concurrentes)\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Limitación de velocidad RPC\u003C\u002Fstrong>: respetar los límites de tasa de los proveedores (ej. 100 solicitudes\u002Fsegundo)\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Control de memoria\u003C\u002Fstrong>: prevenir que las tareas spawneadas se acumulen sin límite\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Control de backpressure\u003C\u002Fstrong>: propagar presión de retorno desde los recursos downstream\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch2 id=\"la-api-de-tokio-sync-semaphore\">La API de tokio::sync::Semaphore\u003C\u002Fh2>\n\u003Cp>La API del semáforo de tokio es mínima pero poderosa:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-rust\">use tokio::sync::Semaphore;\nuse std::sync::Arc;\n\n\u002F\u002F Crear un semáforo con N permisos\nlet sem = Arc::new(Semaphore::new(20));\n\n\u002F\u002F Adquirir un permiso (espera si todos están ocupados)\nlet permit = sem.acquire().await.unwrap();\n\u002F\u002F ... hacer trabajo ...\ndrop(permit); \u002F\u002F Liberar el permiso\n\n\u002F\u002F Intentar adquirir sin esperar\nlet permit = sem.try_acquire();\n\n\u002F\u002F Adquirir con timeout\nlet permit = tokio::time::timeout(\n    Duration::from_secs(5),\n    sem.acquire()\n).await;\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"patr-n-escritura-fire-and-forget-con-sem-foro\">Patrón: escritura fire-and-forget con semáforo\u003C\u002Fh2>\n\u003Cp>Un patrón común en bots de MEV es la escritura fire-and-forget: enviar resultados a la base de datos sin esperar confirmación, pero con contrapresión para no desbordar el pool de conexiones.\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-rust\">struct Writer {\n    pool: PgPool,\n    semaphore: Arc&lt;Semaphore&gt;,\n}\n\nimpl Writer {\n    fn new(pool: PgPool, max_concurrent: usize) -&gt; Self {\n        Self {\n            pool,\n            semaphore: Arc::new(Semaphore::new(max_concurrent)),\n        }\n    }\n    \n    fn write(&amp;self, data: WritePayload) {\n        let pool = self.pool.clone();\n        let sem = self.semaphore.clone();\n        \n        tokio::spawn(async move {\n            \u002F\u002F Esperar por un permiso (backpressure aquí)\n            let _permit = match tokio::time::timeout(\n                Duration::from_secs(10),\n                sem.acquire()\n            ).await {\n                Ok(Ok(permit)) =&gt; permit,\n                _ =&gt; {\n                    tracing::warn!(\"Write semaphore timeout, dropping payload\");\n                    return;\n                }\n            };\n            \n            \u002F\u002F Ejecutar la escritura\n            if let Err(e) = insert_data(&amp;pool, &amp;data).await {\n                tracing::error!(\"Write failed: {}\", e);\n            }\n            \u002F\u002F _permit se libera aquí (RAII)\n        });\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Este patrón es no-bloqueante para el caller — la escritura se ejecuta en una tarea separada. Pero el semáforo previene que se acumulen más escrituras de las que el pool puede manejar.\u003C\u002Fp>\n\u003Ch2 id=\"diagn-stico-de-deadlocks\">Diagnóstico de deadlocks\u003C\u002Fh2>\n\u003Cp>Los deadlocks con semáforos ocurren cuando:\u003C\u002Fp>\n\u003Col>\n\u003Cli>\u003Cstrong>Adquisición anidada\u003C\u002Fstrong>: una tarea que ya tiene un permiso intenta adquirir otro del mismo semáforo\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Dependencia circular\u003C\u002Fstrong>: tarea A espera permiso de semáforo B, tarea B espera permiso de semáforo A\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Permiso retenido a través de await\u003C\u002Fstrong>: una tarea adquiere un permiso, hace un await que nunca resuelve, y nunca libera el permiso\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch3>Detección con tokio-console\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"language-rust\">\u002F\u002F En main.rs\nconsole_subscriber::init();\n\n\u002F\u002F Ejecutar con:\n\u002F\u002F RUSTFLAGS=\"--cfg tokio_unstable\" cargo run\n\u002F\u002F tokio-console\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>tokio-console muestra:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>Tareas bloqueadas esperando semáforos\u003C\u002Fli>\n\u003Cli>Duración del bloqueo\u003C\u002Fli>\n\u003Cli>Stack traces de las tareas bloqueadas\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch3>Detección con tracing\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"language-rust\">use tracing::{instrument, warn};\n\n#[instrument(skip(sem), fields(available = sem.available_permits()))]\nasync fn acquire_with_diagnostics(\n    sem: &amp;Semaphore,\n    label: &amp;str,\n) -&gt; Result&lt;SemaphorePermit&lt;'_&gt;, AcquireError&gt; {\n    let start = Instant::now();\n    let permit = sem.acquire().await?;\n    let elapsed = start.elapsed();\n    \n    if elapsed &gt; Duration::from_secs(1) {\n        warn!(\n            label = label,\n            elapsed_ms = elapsed.as_millis(),\n            \"Slow semaphore acquisition detected\"\n        );\n    }\n    \n    Ok(permit)\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"patr-n-sem-foro-con-timeout-obligatorio\">Patrón: semáforo con timeout obligatorio\u003C\u002Fh2>\n\u003Cp>En producción, nunca adquieras un semáforo sin timeout:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-rust\">async fn safe_acquire(\n    sem: &amp;Semaphore,\n    timeout_secs: u64,\n) -&gt; Result&lt;SemaphorePermit&lt;'_&gt;&gt; {\n    tokio::time::timeout(\n        Duration::from_secs(timeout_secs),\n        sem.acquire()\n    )\n    .await\n    .map_err(|_| anyhow!(\"Semaphore acquire timed out after {}s\", timeout_secs))?\n    .map_err(|e| anyhow!(\"Semaphore closed: {}\", e))\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"permisos-raii-nunca-perder-un-permiso\">Permisos RAII: nunca perder un permiso\u003C\u002Fh2>\n\u003Cp>El patrón RAII (Resource Acquisition Is Initialization) de Rust garantiza que los permisos se liberen incluso si la tarea paniquea:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-rust\">async fn process_with_permit(sem: Arc&lt;Semaphore&gt;, data: Data) {\n    let _permit = sem.acquire().await.unwrap();\n    \u002F\u002F Si process() paniquea, _permit aún se libera\n    \u002F\u002F porque Rust ejecuta drop() al salir del scope\n    process(data).await;\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Esto es fundamentalmente más seguro que el patrón acquire\u002Frelease manual de otros lenguajes, donde un panic entre acquire y release pierde el permiso permanentemente.\u003C\u002Fp>\n\u003Ch2 id=\"patrones-avanzados\">Patrones avanzados\u003C\u002Fh2>\n\u003Ch3>Semáforo ponderado\u003C\u002Fh3>\n\u003Cp>Para operaciones con diferente peso de recurso:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-rust\">let sem = Semaphore::new(100); \u002F\u002F 100 unidades de capacidad\n\n\u002F\u002F Operación pequeña: 1 unidad\nlet _permit = sem.acquire_many(1).await?;\n\n\u002F\u002F Operación grande: 10 unidades\nlet _permit = sem.acquire_many(10).await?;\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>Rate limiter basado en semáforo\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"language-rust\">struct RateLimiter {\n    semaphore: Arc&lt;Semaphore&gt;,\n}\n\nimpl RateLimiter {\n    fn new(max_per_second: usize) -&gt; Self {\n        let sem = Arc::new(Semaphore::new(max_per_second));\n        let sem_clone = sem.clone();\n        \n        \u002F\u002F Reponer permisos cada segundo\n        tokio::spawn(async move {\n            loop {\n                tokio::time::sleep(Duration::from_secs(1)).await;\n                let deficit = max_per_second - sem_clone.available_permits();\n                sem_clone.add_permits(deficit);\n            }\n        });\n        \n        Self { semaphore: sem }\n    }\n    \n    async fn acquire(&amp;self) -&gt; SemaphorePermit&lt;'_&gt; {\n        self.semaphore.acquire().await.unwrap()\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"m-tricas-de-producci-n\">Métricas de producción\u003C\u002Fh2>\n\u003Cp>Monitorea estos indicadores:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Cstrong>available_permits\u003C\u002Fstrong>: si está constantemente en 0, necesitas más capacidad\u003C\u002Fli>\n\u003Cli>\u003Cstrong>acquire_duration_p99\u003C\u002Fstrong>: latencia de adquisición en el percentil 99\u003C\u002Fli>\n\u003Cli>\u003Cstrong>timeout_count\u003C\u002Fstrong>: número de timeouts de adquisición por minuto\u003C\u002Fli>\n\u003Cli>\u003Cstrong>held_duration_p99\u003C\u002Fstrong>: cuánto tiempo se retienen los permisos\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cpre>\u003Ccode class=\"language-rust\">use prometheus::{Histogram, IntGauge};\n\nlet permits_available = IntGauge::new(\n    \"semaphore_permits_available\",\n    \"Number of available semaphore permits\"\n).unwrap();\n\nlet acquire_duration = Histogram::new(\n    \"semaphore_acquire_duration_seconds\",\n    \"Time to acquire a semaphore permit\"\n).unwrap();\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"caso-de-estudio-bot-de-mev\">Caso de estudio: bot de MEV\u003C\u002Fh2>\n\u003Cp>Nuestro bot de MEV usa tres semáforos:\u003C\u002Fp>\n\u003Col>\n\u003Cli>\u003Cstrong>RPC semaphore (50 permisos)\u003C\u002Fstrong>: limita llamadas concurrentes al nodo Ethereum\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Simulation semaphore (20 permisos)\u003C\u002Fstrong>: limita simulaciones EVM concurrentes (intensivas en CPU)\u003C\u002Fli>\n\u003Cli>\u003Cstrong>DB write semaphore (10 permisos)\u003C\u002Fstrong>: limita escrituras concurrentes al pool PostgreSQL\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cpre>\u003Ccode class=\"language-rust\">struct MevBot {\n    rpc_sem: Arc&lt;Semaphore&gt;,\n    sim_sem: Arc&lt;Semaphore&gt;,\n    db_sem: Arc&lt;Semaphore&gt;,\n}\n\nimpl MevBot {\n    async fn process_opportunity(&amp;self, opp: Opportunity) {\n        \u002F\u002F Fase 1: leer estado on-chain\n        let _rpc = self.rpc_sem.acquire().await.unwrap();\n        let state = self.read_state(&amp;opp).await;\n        drop(_rpc);\n        \n        \u002F\u002F Fase 2: simular\n        let _sim = self.sim_sem.acquire().await.unwrap();\n        let result = self.simulate(&amp;opp, state).await;\n        drop(_sim);\n        \n        \u002F\u002F Fase 3: escribir resultado (fire-and-forget)\n        if result.is_profitable() {\n            let db_sem = self.db_sem.clone();\n            tokio::spawn(async move {\n                let _db = db_sem.acquire().await.unwrap();\n                save_result(result).await;\n            });\n        }\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Cada fase libera su permiso antes de que la siguiente lo adquiera, maximizando el paralelismo entre fases.\u003C\u002Fp>\n\u003Ch2 id=\"conclusi-n\">Conclusión\u003C\u002Fh2>\n\u003Cp>Los semáforos son la primitiva de concurrencia esencial para sistemas de alto rendimiento en Rust asíncrono. Proporcionan control de backpressure sin serialización completa, previenen la exhaustión de recursos, y con el patrón RAII de Rust, garantizan la liberación de permisos incluso ante errores. Siempre usa timeouts de adquisición en producción, monitorea los permisos disponibles, y diagnostica los bloqueos con tokio-console y tracing instrumentado.\u003C\u002Fp>\n","es","b0000000-0000-0000-0000-000000000001",true,"2026-03-28T10:44:31.833941Z","Deep EVM #29: Semáforos en Rust Asíncrono — Deadlocks y Fire-and-Forget","Semáforos tokio para backpressure, escrituras fire-and-forget, diagnóstico de deadlocks con tracing, permisos RAII y timeouts de adquisición.","semáforos Rust asíncrono tokio",null,"index, follow",[22,27],{"id":23,"name":24,"slug":25,"created_at":26},"c0000000-0000-0000-0000-000000000022","Performance","performance","2026-03-28T10:44:21.513630Z",{"id":28,"name":29,"slug":30,"created_at":26},"c0000000-0000-0000-0000-000000000001","Rust","rust","Ingeniería",[33,39,45],{"id":34,"title":35,"slug":36,"excerpt":37,"locale":12,"category_name":31,"published_at":38},"d0000000-0000-0000-0000-000000000683","Por qué Bali se está convirtiendo en el hub de impact-tech del Sudeste Asiático en 2026","por-que-bali-hub-impact-tech-sudeste-asiatico-2026","Bali ocupa el puesto 16 entre los ecosistemas startup del Sudeste Asiático. Con una concentración creciente de constructores Web3, startups de AI sostenible y empresas de eco-travel tech, la isla se consolida como capital de impact-tech de la región.","2026-03-28T10:44:49.926489Z",{"id":40,"title":41,"slug":42,"excerpt":43,"locale":12,"category_name":31,"published_at":44},"d0000000-0000-0000-0000-000000000682","El mosaico de protección de datos de ASEAN: checklist de cumplimiento para desarrolladores","mosaico-proteccion-datos-asean-checklist-cumplimiento-desarrolladores","Siete países de ASEAN tienen ahora leyes integrales de protección de datos, cada una con diferentes modelos de consentimiento, requisitos de localización y estructuras de sanciones. Un checklist práctico de cumplimiento para desarrolladores.","2026-03-28T10:44:49.919345Z",{"id":46,"title":47,"slug":48,"excerpt":49,"locale":12,"category_name":31,"published_at":50},"d0000000-0000-0000-0000-000000000681","La transformación digital de 29 mil millones de dólares de Indonesia: oportunidades para empresas de software","transformacion-digital-29-mil-millones-dolares-indonesia-oportunidades-empresas-software","El mercado de servicios IT de Indonesia alcanzará los 29.030 millones de dólares en 2026, frente a los 24.370 millones de 2025. La infraestructura cloud, la AI, el comercio electrónico y los centros de datos impulsan el crecimiento más rápido del Sudeste Asiático.","2026-03-28T10:44:49.897658Z",{"id":13,"name":52,"slug":53,"bio":54,"photo_url":19,"linkedin":19,"role":55,"created_at":56,"updated_at":56},"Open Soft Team","open-soft-team","The engineering team at Open Soft, building premium software solutions from Bali, Indonesia.","Engineering Team","2026-03-28T08:31:22.226811Z"]