Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 28 additions & 1 deletion src/api/deobfuscate.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,33 @@
use axum::Json;
use once_cell::sync::Lazy;
use serde::Deserialize;
use tokio::sync::Semaphore;

use crate::{
deobfuscator::LineEngine,
error::ApiError,
mapping::{download::load_mappings, Mappings},
};

/// Concurrency gate for the heavy load+deobfuscate path.
///
/// There is no mapping cache by design: each concurrent request holds a full
/// per-version table set (~30MB). On memory-constrained hosts, unbounded
/// `spawn_blocking` would multiply that per in-flight request. The semaphore
/// pins peak memory to `SPINYARN_MAX_CONCURRENCY` x one version.
///
/// Default 32: LogShare's real traffic is ~1600 RPM (~27 req/s), which at
/// ~150ms/request means a steady-state concurrency of ~16. A limit of 32 never
/// engages in steady state; it only converts burst OOM risk (~30MB per
/// in-flight request) into short queueing.
static GATE: Lazy<Semaphore> = Lazy::new(|| {
let n = std::env::var("SPINYARN_MAX_CONCURRENCY")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(32);
Semaphore::new(n)
});

#[derive(Debug, Deserialize)]
pub struct DeobfuscateRequest {
pub content: String,
Expand Down Expand Up @@ -51,8 +72,13 @@ pub async fn handler(
return Ok(Json(crate::api::response::ApiResponse::success(passthrough(req))));
}

// Bound peak memory: at most N in-flight mapping table sets.
let _permit = GATE
.acquire()
.await
.map_err(|e| ApiError::Internal(e.to_string()))?;

let version = req.version.clone();
let content = req.content.clone();

// CPU-bound: gzip decompress + parse.
let loaded: Result<Option<Mappings>, ApiError> = tokio::task::spawn_blocking(move || {
Expand All @@ -70,6 +96,7 @@ pub async fn handler(
Err(e) => return Err(e),
};

let content = req.content.clone();
let deobfuscated = tokio::task::spawn_blocking(move || {
let engine = LineEngine::new(mappings);
engine.deobfuscate(&content)
Expand Down
Loading