package support; import java.util.ArrayList; import java.util.List; /** * MOAD-0009: A Metered Heart. * * Defect: a scheduled job fires on a fixed timer interval regardless of whether * any event warrants the execution. Two forms: * (a) State-repair: patches broken state during a visible glitch window * instead of preventing the broken state at its source. * (b) Report-generation: recomputes everything from scratch on each tick * instead of responding to real data-change events. * * Both substitute time-delayed blind repetition for correct event-driven design. * A Metered Heart cannot tell the truth about its own state — it only knows * the clock. Between ticks, the world is wrong and nobody knows. * * Fix: event-driven listener fires exactly once per event, immediately, with * no polling overhead and no stale-state window. * * Scanner detects: Timer.scheduleAtFixedRate, ScheduledExecutorService.scheduleAtFixedRate, * crontab entries, @Scheduled annotations — where the job body reads state and * conditionally acts (the polling tell), rather than receiving an event payload. */ public class Moad0009Algorithm { /** An event with a name and a simulated arrival time (tick). */ public static final class Event { public final String name; public final int arrivalTick; // which timer tick this event arrives on public Event(String name, int arrivalTick) { this.name = name; this.arrivalTick = arrivalTick; } } /** Result: how many times the job fired and how many events it processed. */ public static final class Result { public final int firings; // how many times the job ran public final int eventsProcessed; // how many events were handled public final int totalEvents; // total events that existed public final List processedNames; public Result(int firings, int eventsProcessed, int totalEvents, List processedNames) { this.firings = firings; this.eventsProcessed = eventsProcessed; this.totalEvents = totalEvents; this.processedNames = processedNames; } } // ── Defective: polling scheduler ───────────────────────────────────────── /** * Simulates a timer-driven job that fires every tick for totalTicks ticks. * DEFECT: the job fires even when there are no events to process — wasting * CPU, database I/O, and thread time on empty polls. * * @param totalTicks number of timer firings to simulate * @param events events to be processed (each has an arrivalTick) * @return result showing wasted firings vs useful work */ public static Result runDefectiveScheduler(int totalTicks, List events) { int firings = 0; int eventsProcessed = 0; List processedNames = new ArrayList<>(); for (int tick = 0; tick < totalTicks; tick++) { firings++; // DEFECT: fires every tick, useful or not // Scan for pending events (simulates a DB poll or queue drain) for (Event e : events) { if (e.arrivalTick == tick) { eventsProcessed++; processedNames.add(e.name); } } } return new Result(firings, eventsProcessed, events.size(), processedNames); } // ── Fixed: event-driven listener ───────────────────────────────────────── /** * Simulates an event-driven listener that fires exactly once per event. * FIX: no timer, no polling — the job runs only when an event arrives. * No CPU wasted on empty ticks. No stale-state window between ticks. * * @param events events to process * @return result showing one firing per event, zero wasted firings */ public static Result runFixedEventDriven(List events) { int firings = 0; List processedNames = new ArrayList<>(); // FIX: each event triggers exactly one handler invocation for (Event e : events) { firings++; // fired because an event arrived processedNames.add(e.name); } return new Result(firings, firings, events.size(), processedNames); } /** Build a list of M events spread across [0, maxTick). */ public static List buildEvents(int count, int maxTick) { List events = new ArrayList<>(count); for (int i = 0; i < count; i++) { int tick = (count == 1) ? 0 : (maxTick - 1) * i / (count - 1); events.add(new Event("event-" + i, tick)); } return events; } }