Audit codebase for performance bottlenecks and thread-safety issues
Purpose: Systematically audit Minecraft mod codebase for performance bottlenecks, thread-safety issues, and dimension isolation problems.
When to use:
Output: Generates comprehensive audit report with findings, risk assessment, and fix recommendations.
What to look for:
TickEvent.SERVER_POST or TickEvent.SERVER_LEVEL_POSTworldgen/ packagesworldgen/spawning/Commands:
# Find all event registrations
grep -r "TickEvent\\.SERVER" common/src/main/java --include="*.java"
# Find boss room placers and spawners
find common/src/main/java -name "*BossRoom*.java" -o -name "*Spawner*.java"
# Find structure-related mixins
find common/src/main/java/*/mixin -name "*Structure*.java"
Critical patterns to find:
Large block scans on main thread:
// BAD: Scanning millions of blocks synchronously
for (int x = minX; x <= maxX; x++) {
for (int y = minY; y <= maxY; y++) {
for (int z = minZ; z <= maxZ; z++) {
BlockPos pos = new BlockPos(x, y, z);
BlockState state = level.getBlockState(pos);
// ... processing ...
}
}
}
Red flags:
Repeated full-area scans:
// BAD: Scanning same area multiple times
removeWater(); // Scan 1
placeTemplate();
finalizeWaterlogging(); // Scan 2
scheduledFinalize(); // Scan 3 (later)
Red flags:
Single-tick batch operations:
// BAD: Processing all candidates in one tick
for (PlacementCandidate candidate : allCandidates) {
evaluateCandidate(candidate); // Heavy computation
}
Red flags:
For each issue found, estimate:
| Metric | Measurement | Severity |
|---|---|---|
| Blocks scanned | Count triple-nested loops | > 100K = CRITICAL |
| Scan frequency | Ticks between scans | < 600 = HIGH |
| Pause duration | Estimated ms (1M blocks โ 50-100ms) | > 200ms = CRITICAL |
| Affected players | How many players trigger | All nearby = CRITICAL |
Document findings:
### Issue: [Description]
**File**: `path/to/File.java` (lines X-Y)
**Pattern**: [Large block scan / Repeated scan / Batch operation]
**Impact**:
- Blocks scanned: [count]
- Estimated pause: [ms]
- Frequency: Every [ticks]
**Severity**: [CRITICAL / HIGH / MEDIUM]
Critical patterns to find:
Static HashMap/HashSet in multi-threaded context:
// BAD: HashMap is NOT thread-safe
private static final Map<ResourceLocation, Set<BlockPos>> map = new HashMap<>();
Where to check:
Non-atomic map operations:
// BAD: putIfAbsent + get is NOT atomic as two operations
map.putIfAbsent(key, new HashSet<>());
Set<BlockPos> set = map.get(key); // Race condition here
// BAD: get + increment + put is NOT atomic
int count = map.get(key);
count++;
map.put(key, count); // Lost updates possible
Mixin instance variables:
@Mixin(SomeClass.class)
public class SomeMixin {
// BAD: Instance variable in mixin (shared across threads)
private final Set<BlockPos> positions = new HashSet<>();
}
For each collection found, check:
Questions:
Thread-safe patterns:
// GOOD: ConcurrentHashMap for maps
private static final Map<K, V> map = new ConcurrentHashMap<>();
// GOOD: ConcurrentHashMap.newKeySet() for sets
private static final Set<BlockPos> set = ConcurrentHashMap.newKeySet();
// GOOD: Collections.newSetFromMap for sets
private static final Set<BlockPos> set = Collections.newSetFromMap(new ConcurrentHashMap<>());
// GOOD: Collections.synchronizedSet for sets
private final Set<BlockPos> set = Collections.synchronizedSet(new HashSet<>());
// GOOD: Atomic increment
map.compute(key, (k, v) -> (v == null ? 0 : v) + 1);
// GOOD: Atomic putIfAbsent for sets
map.computeIfAbsent(key, k -> ConcurrentHashMap.newKeySet());
Document findings:
### Issue: Non-thread-safe [HashMap/HashSet/ArrayList]
**File**: `path/to/File.java` (lines X-Y)
**Pattern**:
```java
private static final Map<K, V> map = new HashMap<>();
Risk: [ConcurrentModificationException / Data corruption / Lost updates]
Fix: Replace with ConcurrentHashMap
Severity: [CRITICAL / HIGH / MEDIUM]
---
### Phase 4: Dimension Isolation Analysis (T430)
#### 4.1 Cross-Dimension Processing Detection
**Critical patterns to find**:
1. **Processing all dimensions without filtering**:
```java
// BAD: Processes ALL dimensions
TickEvent.SERVER_POST.register(server -> {
for (ServerLevel level : server.getAllLevels()) {
processLevel(level); // No dimension check!
}
});
// BAD: No early exit for wrong dimension
public static void checkAndProcess(ServerLevel level) {
// Immediately starts processing without dimension check
for (...) { ... }
}
GOOD patterns:
// GOOD: Filter to specific dimension
TickEvent.SERVER_LEVEL_POST.register(level -> {
if (!level.dimension().equals(ModDimensions.CHRONO_DAWN_DIMENSION)) {
return; // Early exit
}
// ... process only Chrono Dawn
});
For each handler found, check:
Questions:
SERVER_POST (global) or SERVER_LEVEL_POST (per-dimension)?Expected behavior:
Document findings:
### Issue: No dimension filtering
**File**: `path/to/File.java` (lines X-Y)
**Pattern**: Processes all dimensions without filter
**Impact**: Wasted CPU cycles in [Overworld/Nether/End]
**Expected**: Only process [Chrono Dawn] dimension
**Fix**: Add early-exit dimension filter
**Severity**: MEDIUM
# Performance and Thread Safety Audit Report
**Date**: [YYYY-MM-DD]
**Branch**: [branch-name]
**Tasks**: T428 (Performance), T429 (Thread Safety), T430 (Dimension Isolation)
---
## Executive Summary
[Brief overview of findings, priority, risk]
---
## T428: Main Thread Blocking in Structure Generation
### Findings
[Detailed findings from Phase 2]
### Root Cause Analysis
[Why these issues exist]
### Recommended Fixes
[Prioritized fix recommendations]
---
## T429: Non-Thread-Safe Collection Usage
### Findings
[Detailed findings from Phase 3]
### Root Cause Analysis
[Why these issues exist]
### Recommended Fixes
[Prioritized fix recommendations]
---
## T430: Dimension Filtering in Chunk Processing
### Findings
[Detailed findings from Phase 4]
### Root Cause Analysis
[Why these issues exist]
### Recommended Fixes
[Prioritized fix recommendations]
---
## Summary of Issues Found
[Table of all issues with severity, files, priority]
---
## Next Steps
[Recommended actions in priority order]
---
## Estimated Effort
[Time estimates for fixes]
---
## Risk Assessment
[What happens without fixes vs with fixes]
CRITICAL:
HIGH:
MEDIUM:
LOW:
Problem: Scanning millions of blocks on main thread Solution:
Example fix:
// Before: Scan all at once
for (BlockPos pos : BlockPos.betweenClosed(min, max)) { ... }
// After: Process 1 chunk per tick
private static ChunkPos currentChunk = null;
private static Iterator<BlockPos> scanIterator = null;
public static void onTick(ServerLevel level) {
if (scanIterator == null) {
// Start new scan
currentChunk = ...;
scanIterator = BlockPos.betweenClosed(...).iterator();
}
int processed = 0;
while (scanIterator.hasNext() && processed < 256) { // Limit per tick
BlockPos pos = scanIterator.next();
// ... process ...
processed++;
}
if (!scanIterator.hasNext()) {
scanIterator = null; // Done
}
}
Problem: HashMap/HashSet accessed from multiple threads Solution: Replace with ConcurrentHashMap
Example fix:
// Before:
private static final Map<ResourceLocation, Set<BlockPos>> map = new HashMap<>();
// After:
private static final Map<ResourceLocation, Set<BlockPos>> map = new ConcurrentHashMap<>();
// Before (non-atomic):
map.putIfAbsent(key, new HashSet<>());
Set<BlockPos> set = map.get(key);
// After (atomic):
Set<BlockPos> set = map.computeIfAbsent(key, k -> ConcurrentHashMap.newKeySet());
// Before (non-atomic increment):
int count = map.get(key);
count++;
map.put(key, count);
// After (atomic increment):
int count = map.compute(key, (k, v) -> (v == null ? 0 : v) + 1);
Problem: Processing all dimensions when structure only spawns in one Solution: Add early-exit dimension filter
Example fix:
// Before:
public static void checkAndProcess(ServerLevel level) {
ResourceLocation dimensionId = level.dimension().location();
// ... process ...
}
// After:
public static void checkAndProcess(ServerLevel level) {
// Only process Chrono Dawn dimension
if (!level.dimension().equals(ModDimensions.CHRONO_DAWN_DIMENSION)) {
return;
}
ResourceLocation dimensionId = level.dimension().location();
// ... process ...
}
After applying fixes:
:common:compileJava, :fabric:compileJava, :neoforge:compileJava)Related Documentation:
CLAUDE.md โ "Structure Worldgen Guidelines" (for structure generation best practices).claude/skills/structure-worldgen/SKILL.md (for waterlogging prevention patterns)Related Tasks:
Example Audit Reports:
audit_report_performance_thread_safety.md (example report)FIXES_APPLIED.md (example fixes summary)Last Updated: 2026-01-02 Author: Claude Sonnet 4.5