package support; /** * MOAD-0003: A Leaked Context. * * Defect: request-scoped identity (user principal, tenant ID, transaction state) * stored in a ThreadLocal whose lifetime is the thread, not the unit of work. * When a thread-pool thread is reused for a second request, stale identity from * the first request bleeds into the second — even though the second request * never set any identity. * * Fix: always call ThreadLocal.remove() in a finally block after the request * completes. Alternatively, pass context explicitly as a parameter (preferred). * * Scanner detects: ThreadLocal fields without a paired remove() call in a * finally block on the same code path that calls set(). */ public class Moad0003Algorithm { /** The ambient carrier — shared across the simulated thread pool. */ public static final ThreadLocal REQUEST_IDENTITY = new ThreadLocal<>(); // ── Defective: ThreadLocal set but never removed ─────────────────────── /** * Simulates handling one HTTP request in the defective pattern. * Sets the ThreadLocal to 'identity' and processes the request. * DEFECT: never calls remove() — stale identity remains on the thread. * * @return the identity seen during this request */ public static String handleDefective(String identity) { if (identity != null) { REQUEST_IDENTITY.set(identity); } // ... simulate request processing ... return REQUEST_IDENTITY.get(); // DEFECT: no REQUEST_IDENTITY.remove() — next request sees this identity } /** * Simulates the next request on the same pooled thread. * The caller does NOT set an identity (anonymous request). * DEFECT: returns whatever the previous request left behind. * * @return the identity seen — should be null but is the previous caller's identity */ public static String handleDefectiveAnonymous() { // Does not call REQUEST_IDENTITY.set() — anonymous request return REQUEST_IDENTITY.get(); // DEFECT: returns stale identity from previous request } // ── Fixed: ThreadLocal always removed in finally ─────────────────────── /** * Simulates handling one HTTP request in the fixed pattern. * Sets the ThreadLocal, processes, then ALWAYS removes in finally. * * @return the identity seen during this request */ public static String handleFixed(String identity) { if (identity != null) { REQUEST_IDENTITY.set(identity); } try { return REQUEST_IDENTITY.get(); } finally { REQUEST_IDENTITY.remove(); // FIX: always clean up, even on exception } } /** * Simulates the next request on the same pooled thread (anonymous). * After the fixed handler ran, the ThreadLocal is null. * * @return the identity seen — null (clean) */ public static String handleFixedAnonymous() { return REQUEST_IDENTITY.get(); // FIX: null — previous request cleaned up } /** Reset ThreadLocal between test cases. */ public static void reset() { REQUEST_IDENTITY.remove(); } }