Skip to content

refactor(concurrency): improve thread management and locking in monitoring services#137

Merged
balazs-szucs merged 3 commits into
grimmory-tools:developfrom
balazs-szucs:mem-stuff
Mar 23, 2026
Merged

refactor(concurrency): improve thread management and locking in monitoring services#137
balazs-szucs merged 3 commits into
grimmory-tools:developfrom
balazs-szucs:mem-stuff

Conversation

@balazs-szucs

@balazs-szucs balazs-szucs commented Mar 23, 2026

Copy link
Copy Markdown
Contributor

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:

  • Replaced synchronized blocks with ReentrantLock in BookdropMonitoringService, LibraryWatchService, and DefaultSettingInitializer to ensure more granular and scalable thread safety, especially in multi-threaded environments.

Database and Hibernate Tuning:

  • Enabled @DynamicUpdate on large entity tables (BookMetadataEntity, ComicMetadataEntity) to reduce SQL update overhead by only including changed columns.
  • Tuned Hibernate and HikariCP settings in application.yaml to optimize for virtual threads, reduce connection pool size, improve batching, and minimize query plan cache bloat.

Docker and Developer Workflow Enhancements:

  • Added comprehensive documentation for building, running, and testing the production Docker image locally, including tips for cross-platform builds and resource management.
  • Updated the Dockerfile to use the Shenandoah GC with more conservative memory settings, improving container performance and reliability.

Other Notable Changes:

  • Simplified XML parsing in EpubReaderService by removing thread-local DocumentBuilder in favor of per-call instantiation, eliminating potential concurrency issues.
  • Minor code organization and bug fixes, such as adjusting the scope of a try block in HardcoverSyncService for correct API token management.

Summary by CodeRabbit

  • Performance Improvements

    • Optimized database query execution and connection pool efficiency
    • Enhanced garbage collection and memory management configuration
    • Improved concurrent request handling through thread pool tuning
  • Documentation

    • Added comprehensive Docker build and deployment instructions for contributors

The release includes internal optimizations focused on improving application performance, stability, and resource utilization.

@coderabbitai

coderabbitai Bot commented Mar 23, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

This PR introduces performance and concurrency optimizations across the application: replacing method-level synchronized blocks with explicit ReentrantLock instances in multiple services, changing the JVM garbage collector from G1GC to ShenandoahGC with adjusted memory percentages, adding Hibernate and Tomcat configuration tuning, modifying XML parsing to remove thread-local reuse, and updating documentation with Docker build instructions.

Changes

Cohort / File(s) Summary
Documentation
CONTRIBUTING.md
Added comprehensive "Building the Production Docker Image" section with multi-stage Docker build instructions, MariaDB integration steps, environment variable configuration, volume mounting guidance, cleanup commands, cross-platform buildx examples, and tips for memory limiting and log access.
Runtime Configuration
Dockerfile, booklore-api/src/main/resources/application.yaml
Updated JVM options: replaced G1GC with ShenandoahGC, changed MaxRAMPercentage from 75% to 60%, added InitialRAMPercentage of 8%. Added Tomcat thread limits (max 20, min-spare 4), reduced HikariCP pool sizes (max 10, min-idle 2), and added Hibernate query plan caching and JDBC batching/fetch tuning properties.
Entity Optimization
booklore-api/src/main/java/org/booklore/.../BookMetadataEntity.java, booklore-api/src/main/java/org/booklore/.../ComicMetadataEntity.java
Added @DynamicUpdate annotation to generate SQL UPDATE statements including only modified columns rather than all mapped columns.
Synchronization Refactoring
booklore-api/src/main/java/org/booklore/service/bookdrop/BookdropMonitoringService.java, booklore-api/src/main/java/org/booklore/service/monitoring/LibraryWatchService.java, booklore-api/src/main/java/org/booklore/service/user/DefaultSettingInitializer.java
Replaced method-level synchronized blocks with explicit ReentrantLock instances; refined critical sections to guard only state mutations and shared resource access; added state transition checks to prevent redundant operations (pause-when-paused, resume-when-unpaused); per-user locking in DefaultSettingInitializer with cleanup on unlock.
Service Logic Adjustments
booklore-api/src/main/java/org/booklore/service/reader/EpubReaderService.java, booklore-api/src/main/java/org/booklore/service/hardcover/HardcoverSyncService.java
Removed static ThreadLocal<DocumentBuilder> reuse and replaced with per-parse factory method. Moved currentApiToken.set() from outer to inner try block scope in HardcoverSyncService.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰 Hop-skip through locks so fine and neat,
G1 to Shenandoah, memory optimized, complete!
Dynamic updates spare the columns unused,
Batches and caches, performance improved!
ThreadLocal gone, fresh builders in place—
This eager rabbit loves a faster pace!

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 8.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ⚠️ Warning The PR description is incomplete and only contains the template structure with no actual content filled in. Complete the description by filling in the 'Changes' section with a detailed summary of the modifications made across all affected files, and add a linked issue number if applicable.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main theme of the PR, which focuses on refactoring concurrency and thread management in monitoring services.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands and usage tips.

@balazs-szucs balazs-szucs marked this pull request as ready for review March 23, 2026 15:07

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 | 🟠 Major

Race condition: removing lock from map while other threads may hold or await it.

The pattern of removing the lock from userLocks immediately after unlocking (line 50) introduces a race:

  1. Thread A acquires lock for userId=1
  2. Thread B calls computeIfAbsent for userId=1, gets same lock, blocks on lock()
  3. Thread A unlocks and removes the lock from map
  4. Thread C calls computeIfAbsent for userId=1, creates a new lock, acquires it
  5. 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

📥 Commits

Reviewing files that changed from the base of the PR and between b6a2394 and 564b5be.

📒 Files selected for processing (10)
  • CONTRIBUTING.md
  • Dockerfile
  • booklore-api/src/main/java/org/booklore/model/entity/BookMetadataEntity.java
  • booklore-api/src/main/java/org/booklore/model/entity/ComicMetadataEntity.java
  • booklore-api/src/main/java/org/booklore/service/bookdrop/BookdropMonitoringService.java
  • booklore-api/src/main/java/org/booklore/service/hardcover/HardcoverSyncService.java
  • booklore-api/src/main/java/org/booklore/service/monitoring/LibraryWatchService.java
  • booklore-api/src/main/java/org/booklore/service/reader/EpubReaderService.java
  • booklore-api/src/main/java/org/booklore/service/user/DefaultSettingInitializer.java
  • booklore-api/src/main/resources/application.yaml

Comment thread CONTRIBUTING.md
@balazs-szucs balazs-szucs merged commit 3c7550c into grimmory-tools:develop Mar 23, 2026
10 checks passed
cdome added a commit to cdome/ollumi that referenced this pull request Mar 25, 2026
…oring services (grimmory-tools#137)

* refactor(concurrency): improve thread management and locking in monitoring services

* chore: remove unnecessary comments
zachyale pushed a commit to zachyale/grimmory that referenced this pull request Apr 17, 2026
…oring services (grimmory-tools#137)

* refactor(concurrency): improve thread management and locking in monitoring services

* chore: remove unnecessary comments
zachyale pushed a commit to zachyale/grimmory that referenced this pull request Apr 17, 2026
…oring services (grimmory-tools#137)

* refactor(concurrency): improve thread management and locking in monitoring services

* chore: remove unnecessary comments
zachyale pushed a commit that referenced this pull request Apr 17, 2026
…oring services (#137)

* refactor(concurrency): improve thread management and locking in monitoring services

* chore: remove unnecessary comments
zachyale pushed a commit that referenced this pull request Apr 22, 2026
…oring services (#137)

* refactor(concurrency): improve thread management and locking in monitoring services

* chore: remove unnecessary comments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant