refactor(concurrency): improve thread management and locking in monitoring services#137
Conversation
📝 WalkthroughWalkthroughThis PR introduces performance and concurrency optimizations across the application: replacing method-level Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
booklore-api/src/main/java/org/booklore/service/user/DefaultSettingInitializer.java (1)
37-51:⚠️ Potential issue | 🟠 MajorRace condition: removing lock from map while other threads may hold or await it.
The pattern of removing the lock from
userLocksimmediately after unlocking (line 50) introduces a race:
- Thread A acquires lock for userId=1
- Thread B calls
computeIfAbsentfor userId=1, gets same lock, blocks onlock()- Thread A unlocks and removes the lock from map
- Thread C calls
computeIfAbsentfor userId=1, creates a new lock, acquires it- Thread B unblocks (on the old lock) and enters critical section simultaneously with Thread C
Either keep the lock in the map permanently (acceptable since user IDs are bounded) or use a reference-counted approach that only removes when no threads are waiting.
🔒 Proposed fix: keep locks in map (simplest safe approach)
initializedUsers.add(bookLoreUser.getId()); } finally { lock.unlock(); - userLocks.remove(bookLoreUser.getId()); } }If memory is a concern due to unbounded user growth, consider a more sophisticated eviction strategy with reference counting.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@booklore-api/src/main/java/org/booklore/service/user/DefaultSettingInitializer.java` around lines 37 - 51, The code removes the per-user lock from userLocks after unlocking, creating a race where waiting threads may use a stale lock; keep the lock in the map instead of removing it. Modify DefaultSettingInitializer by deleting the userLocks.remove(bookLoreUser.getId()) call in the finally block (leaving Lock lock = userLocks.computeIfAbsent(...) and lock.lock()/lock.unlock() as-is) so locks remain cached per userId; if desired later, implement a reference-counted eviction, but for now simply stop removing the lock to fix the race.
🧹 Nitpick comments (1)
booklore-api/src/main/resources/application.yaml (1)
34-36: Configuration aligns well with virtual threads, but monitor connection pool under load.The reduced Tomcat thread count (20) and HikariCP pool size (10) are appropriate when virtual threads handle concurrency. However, virtual threads don't magically increase database throughput—if all 10 connections are in use, additional virtual threads will block waiting for a connection.
Consider adding HikariCP metrics/logging to monitor connection wait times under production load. If you observe high contention, the pool may need adjustment.
Also applies to: 63-64
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@booklore-api/src/main/resources/application.yaml` around lines 34 - 36, Reduce risk of DB contention by enabling HikariCP metrics and logging so you can monitor connection wait times when using virtual threads (you've set threads.max and threads.min-spare low); specifically, enable Hikari metrics for the datasource (reference spring.datasource.hikari.maximum-pool-size and the HikariDataSource/HikariPoolMXBean APIs), register MBeans or wire Micrometer metrics (e.g., register-mbeans / metricsTrackerClassName) and log/alert on connection waitTime/connectionTimeout and activeConnections; if metrics show high contention, increase spring.datasource.hikari.maximum-pool-size or tune connectionTimeout accordingly and add an alert threshold for wait times.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@booklore-api/src/main/java/org/booklore/service/reader/EpubReaderService.java`:
- Around line 61-67: Change createDocumentBuilder() to declare and throw
ParserConfigurationException instead of wrapping it in RuntimeException; update
parseXmlEntry(...) and parseEpubWithEncoding(...) signatures to propagate that
checked exception; then modify parseEpubMetadata(...) so the code that
initializes the parser (calls to
createDocumentBuilder/parseXmlEntry/parseEpubWithEncoding) is performed outside
or before the charset-retry loop and any ParserConfigurationException is caught
and rethrown or handled immediately (not treated as an encoding failure), while
the charset retry loop only catches IO/encoding-related exceptions.
In `@CONTRIBUTING.md`:
- Around line 234-244: The docker run example mixes host networking and a port
mapping which is redundant: remove the -p 6060:6060 token from the docker run
command (or alternatively remove --network host if you prefer bridge networking
and keep -p, remembering to update the datasource URL away from localhost);
update the example so it uses either "--network host" alone or "-p 6060:6060"
without "--network host" so readers aren’t confused.
---
Outside diff comments:
In
`@booklore-api/src/main/java/org/booklore/service/user/DefaultSettingInitializer.java`:
- Around line 37-51: The code removes the per-user lock from userLocks after
unlocking, creating a race where waiting threads may use a stale lock; keep the
lock in the map instead of removing it. Modify DefaultSettingInitializer by
deleting the userLocks.remove(bookLoreUser.getId()) call in the finally block
(leaving Lock lock = userLocks.computeIfAbsent(...) and
lock.lock()/lock.unlock() as-is) so locks remain cached per userId; if desired
later, implement a reference-counted eviction, but for now simply stop removing
the lock to fix the race.
---
Nitpick comments:
In `@booklore-api/src/main/resources/application.yaml`:
- Around line 34-36: Reduce risk of DB contention by enabling HikariCP metrics
and logging so you can monitor connection wait times when using virtual threads
(you've set threads.max and threads.min-spare low); specifically, enable Hikari
metrics for the datasource (reference spring.datasource.hikari.maximum-pool-size
and the HikariDataSource/HikariPoolMXBean APIs), register MBeans or wire
Micrometer metrics (e.g., register-mbeans / metricsTrackerClassName) and
log/alert on connection waitTime/connectionTimeout and activeConnections; if
metrics show high contention, increase
spring.datasource.hikari.maximum-pool-size or tune connectionTimeout accordingly
and add an alert threshold for wait times.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: f4425dbd-6458-47ae-a1d9-e934f8f96431
📒 Files selected for processing (10)
CONTRIBUTING.mdDockerfilebooklore-api/src/main/java/org/booklore/model/entity/BookMetadataEntity.javabooklore-api/src/main/java/org/booklore/model/entity/ComicMetadataEntity.javabooklore-api/src/main/java/org/booklore/service/bookdrop/BookdropMonitoringService.javabooklore-api/src/main/java/org/booklore/service/hardcover/HardcoverSyncService.javabooklore-api/src/main/java/org/booklore/service/monitoring/LibraryWatchService.javabooklore-api/src/main/java/org/booklore/service/reader/EpubReaderService.javabooklore-api/src/main/java/org/booklore/service/user/DefaultSettingInitializer.javabooklore-api/src/main/resources/application.yaml
…oring services (grimmory-tools#137) * refactor(concurrency): improve thread management and locking in monitoring services * chore: remove unnecessary comments
…oring services (grimmory-tools#137) * refactor(concurrency): improve thread management and locking in monitoring services * chore: remove unnecessary comments
…oring services (grimmory-tools#137) * refactor(concurrency): improve thread management and locking in monitoring services * chore: remove unnecessary comments
…oring services (#137) * refactor(concurrency): improve thread management and locking in monitoring services * chore: remove unnecessary comments
…oring services (#137) * refactor(concurrency): improve thread management and locking in monitoring services * chore: remove unnecessary comments
Description
Linked Issue: Fixes #
Changes
This pull request introduces several improvements and optimizations across the codebase, focusing on concurrency safety, database efficiency, and developer workflow enhancements. The most significant changes are the replacement of Java synchronized blocks with explicit locks for thread safety, Hibernate and connection pool tuning for better performance, and improved documentation for building and running the Docker production image.
Concurrency and Thread Safety Improvements:
synchronizedblocks withReentrantLockinBookdropMonitoringService,LibraryWatchService, andDefaultSettingInitializerto ensure more granular and scalable thread safety, especially in multi-threaded environments.Database and Hibernate Tuning:
@DynamicUpdateon large entity tables (BookMetadataEntity,ComicMetadataEntity) to reduce SQL update overhead by only including changed columns.application.yamlto optimize for virtual threads, reduce connection pool size, improve batching, and minimize query plan cache bloat.Docker and Developer Workflow Enhancements:
Other Notable Changes:
EpubReaderServiceby removing thread-localDocumentBuilderin favor of per-call instantiation, eliminating potential concurrency issues.HardcoverSyncServicefor correct API token management.Summary by CodeRabbit
Performance Improvements
Documentation
The release includes internal optimizations focused on improving application performance, stability, and resource utilization.