{"record":{"id":"4e6b1bba6a4fc322","repo":"airbnb/epoxy","slug":"notifications-already-paused","errorCode":null,"errorMessage":"Notifications already paused","messagePattern":"Notifications already paused","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"epoxy-adapter/src/main/java/com/airbnb/epoxy/ModelList.java","lineNumber":39,"sourceCode":"  ModelList(int expectedModelCount) {\n    super(expectedModelCount);\n  }\n\n  ModelList() {\n\n  }\n\n  interface ModelListObserver {\n    void onItemRangeInserted(int positionStart, int itemCount);\n    void onItemRangeRemoved(int positionStart, int itemCount);\n  }\n\n  private boolean notificationsPaused;\n  private ModelListObserver observer;\n\n  void pauseNotifications() {\n    if (notificationsPaused) {\n      throw new IllegalStateException(\"Notifications already paused\");\n    }\n    notificationsPaused = true;\n  }\n\n  void resumeNotifications() {\n    if (!notificationsPaused) {\n      throw new IllegalStateException(\"Notifications already resumed\");\n    }\n    notificationsPaused = false;\n  }\n\n  void setObserver(ModelListObserver observer) {\n    this.observer = observer;\n  }\n\n  private void notifyInsertion(int positionStart, int itemCount) {\n    if (!notificationsPaused && observer != null) {\n      observer.onItemRangeInserted(positionStart, itemCount);","sourceCodeStart":21,"sourceCodeEnd":57,"githubUrl":"https://github.com/airbnb/epoxy/blob/e45bd3a61fe3a1f130e184f5b8dcf172ab99025a/epoxy-adapter/src/main/java/com/airbnb/epoxy/ModelList.java#L21-L57","documentation":"ModelList.pauseNotifications is internal and idempotency-guarded: if notifications are already paused it throws IllegalStateException. This prevents double-pausing (typically two nested batchUpdate calls in Epoxy's controller/diffing machinery) from unbalancing pause/resume pairs.","triggerScenarios":"Calling pauseNotifications() when notificationsPaused is already true — usually two overlapping batch updates on the same ModelList.","commonSituations":"Re-entrant model builds/updates (setModels called inside a notification callback); concurrent threads touching the same controller's model list; misuse of internal APIs.","solutions":["Ensure pause/resume are strictly paired (pause once, resume once)","Avoid calling setModels or mutating the list from inside change notifications","Synchronize list updates to a single thread","Do not call internal ModelList.pauseNotifications from app code"],"exampleFix":"// before\nlist.pauseNotifications();\nlist.pauseNotifications(); // throws\n// after\nif (!isPaused()) { list.pauseNotifications(); }\ntry { /* mutate */ } finally { list.resumeNotifications(); }","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try { list.pauseNotifications(); } catch (IllegalStateException e) { Log.w(TAG, \"already paused\", e); }","preventionTips":["Pair every pause with exactly one resume in try/finally","Never call internal ModelList methods from app code","Avoid re-entrant setModels from notification callbacks","Keep list mutations on a single thread"],"tags":["android","epoxy","model-list","reentrancy"],"backgroundTag":"invalid-state-transition","analyzedSha":"e45bd3a61fe3a1f130e184f5b8dcf172ab99025a","analyzedAt":"2026-09-13T03:24:16.052Z","contentChangedAt":"2026-09-13T03:24:16.052Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}