Problem
Each scope persist allocates two Runnables: the task lambda at the call site (e.g. addBreadcrumb, setTrace, …) and a second wrapper lambda inside serializeToDisk that adds try/catch before submit:
options.getExecutorService().submit(
() -> {
try {
task.run();
} catch (Throwable e) {
options.getLogger().log(ERROR, "Serialization task failed", e);
}
});
Across ~3,130 scope stores this is a steady source of short-lived allocations / GC pressure.
Evidence
From on-device method trace analysis (median.perfetto-trace): every scope mutation goes through serializeToDisk; the wrapper is allocated per call even though it is identical each time.
Fix
Collapse the try/catch into a single reusable wrapper (e.g. a small private helper runSafely(task) referenced once) so only the call-site task Runnable is allocated, not a second wrapper per call. Low priority / minor, but cheap and on a hot path.
Acceptance
One Runnable allocation per persist instead of two; error handling behavior unchanged; existing persistence tests pass.
Problem
Each scope persist allocates two
Runnables: the task lambda at the call site (e.g.addBreadcrumb,setTrace, …) and a second wrapper lambda insideserializeToDiskthat adds try/catch beforesubmit:Across ~3,130 scope stores this is a steady source of short-lived allocations / GC pressure.
Evidence
From on-device method trace analysis (
median.perfetto-trace): every scope mutation goes throughserializeToDisk; the wrapper is allocated per call even though it is identical each time.Fix
Collapse the try/catch into a single reusable wrapper (e.g. a small private helper
runSafely(task)referenced once) so only the call-site task Runnable is allocated, not a second wrapper per call. Low priority / minor, but cheap and on a hot path.Acceptance
One Runnable allocation per persist instead of two; error handling behavior unchanged; existing persistence tests pass.