graphql-java / graphql-java/java-dataloader
[Bug] Memory Leak and Stats Pollution via unmanaged ThreadLocal in ThreadLocalStatisticsCollector
Chưa có ai nhận issue này.
- Ngôn ngữ chính
- Java
- Star
- 525
- Fork
- 101
- Chỉ số merge pull request
- Không có pull request nào được merge trong 30 ngày
Mô tả
Describe the bug
There is a potential Unintentional ThreadLocal Leak (UTL) in ThreadLocalStatisticsCollector. The class relies on consumers manually calling resetThread() at "request boundaries" to clear the ThreadLocal<SimpleStatisticsCollector>.
However, in typical GraphQL environments (reactive streams, async gateways, or standard thread pools), threads are heavily reused. If an unhandled exception occurs or a developer forgets to call resetThread(), the SimpleStatisticsCollector object is permanently retained by the worker thread.
Impact:
- Data Pollution: Subsequent requests processed by the dirty thread will inherit the accumulated statistics of previous requests, leading to completely distorted metrics.
- Memory Leak (Type I UTL): As more threads in the pool retain these un-cleared statistics objects over time, the heap usage will grow linearly, potentially leading to an
OutOfMemoryErrorin high-throughput applications.
To Reproduce
Here is a simplified code example demonstrating the data pollution in a thread-pool environment when resetThread() is missed (e.g., bypassed due to an exception):
import org.dataloader.stats.ThreadLocalStatisticsCollector;
import java.util.concurrent.*;
public class UTLReproduction {
public static void main(String[] args) throws Exception {
ThreadLocalStatisticsCollector collector = new ThreadLocalStatisticsCollector();
// Simulate a web server with a reusable thread pool
ExecutorService threadPool = Executors.newFixedThreadPool(1);
// Request 1: Execution completes but resetThread() is missed (e.g. exception thrown)
threadPool.submit(() -> {
collector.incrementLoadCount();
// Developer forgets to put collector.resetThread() in a finally block
}).get();
// Request 2: A new incoming request reuses the same dirty thread
threadPool.submit(() -> {
long loadCount = collector.getStatistics().getLoadCount();
// BUG: loadCount is 1 instead of 0! The new request is polluted by Request 1.
System.out.println("New Request Load Count (Expected 0): " + loadCount);
}).get();
threadPool.shutdown();
}
}
Hướng dẫn đóng góp
Chưa lập chỉ mục được hướng dẫn đóng góp cho kho mã nguồn này
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Hướng nghiên cứu
Bắt đầu bằng cách đọc ThreadLocalStatisticsCollector, đặc biệt là cách sử dụng ThreadLocal, getStatistics() và resetThread(). Chạy bản tái hiện được cung cấp với một fixed-thread-pool để xác minh liệu các thống kê có tồn tại giữa các requests khi bỏ qua resetThread() hay không. Done phải bao gồm một cách đã được quyết định để ngăn chặn việc nhiễm bẩn và lưu giữ giữa các requests, cùng với các test bao phủ kịch bản đã tái hiện.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Đánh giá
- Công nghệ
- java
- Lĩnh vực
- backend, observability
- Loại issue
- Lỗi
- Độ khó
- 4/5
- Thời gian dự kiến
- 3-5 ngày
- Mức độ hoạt động
- Ít trao đổi
- Độ rõ ràng
- Cần làm rõ
- Mức phù hợp với người mới
- 35/100