Multithreading and Code Quality Review¶
Status: COMPLETE¶
Completed: 2026-02-19
All 24 categories of multithreading and code quality findings have been addressed across 54 files (+1354/-1007 lines).
Executive Summary¶
A comprehensive review of the TQPro codebase identified and resolved multithreading safety issues, resource leaks, and code quality problems across all modules. The changes ensure thread-safe operation in both single-instance and multi-instance (clustered) deployments.
1. Hibernate Session Leak Fixes¶
Problem¶
Over 50 methods across 12 files opened Hibernate sessions without guaranteed cleanup. If an exception occurred between openSession() and session.close(), the session (and its database connection) would leak.
Solution¶
Wrapped all session usage in try-finally blocks to guarantee cleanup:
// Before (unsafe)
Session session = NTSDBSession.openSession();
List<?> results = session.createQuery("...").list();
session.close();
// After (safe)
Session session = NTSDBSession.openSession();
try {
List<?> results = session.createQuery("...").list();
return results;
} finally {
session.close();
}
Affected Files¶
| Module | File | Methods Fixed |
|---|---|---|
| tqapp | NTSCruiseService.java |
~15 methods |
| tqapp | NTSGroupService.java |
~10 methods |
| tqapp | NTSEntityReadService.java |
2 methods |
| tqapp | NTSEntityWriteService.java |
3 methods |
| tqapp | NTSHotelService.java |
~5 methods |
| tqapp | NTSMarketingService.java |
~3 methods |
| tqapp | NTSHotelFacade.java |
~3 methods |
| tqapp | CatalogFacade.java |
~3 methods |
| tqamds | AmdDBSession.java |
Session factory management |
| tqryb2b | RaynaDBSession.java |
Session factory management |
| tqtiqets | TiqetsDBSession.java |
Session factory management |
| tqgglbl | GoGlobalDBSession.java |
Session factory management |
2. Transaction Rollback Safety¶
Problem¶
Database transactions were committed but not rolled back on exception. If an error occurred after beginTransaction() but before commit(), the transaction would remain open, potentially blocking other operations.
Solution¶
Added transaction.rollback() in catch/finally blocks:
Transaction tx = session.beginTransaction();
try {
// ... operations ...
tx.commit();
} catch (Exception e) {
if (tx != null && tx.isActive()) {
tx.rollback();
}
throw e;
}
Affected Files¶
| File | Methods Fixed |
|---|---|
NTSEntityWriteService.java |
create, update, delete |
NTSCruiseService.java |
~5 write methods |
NTSGroupService.java |
~4 write methods |
NTSHotelService.java |
~2 write methods |
NTSMarketingService.java |
~1 write method |
CatalogFacade.java |
catalog update methods |
3. Session Sharing to Avoid Multiple Opens¶
Problem¶
Some methods opened multiple Hibernate sessions for related operations within the same logical unit of work, wasting connections and risking inconsistency.
Solution¶
Refactored to pass a shared session to internal methods:
| File | Change |
|---|---|
NTSCruiseService.java |
Sub-queries share caller's session |
NTSGroupService.java |
Related entity loads share session |
CatalogFacade.java |
Batch operations share session |
4. Concurrent Data Structure Upgrades¶
Problem¶
Several HashMap instances were accessed from multiple threads without synchronization, risking data corruption (infinite loops in JDK 7, data loss in JDK 8+).
Solution¶
Replaced with ConcurrentHashMap:
| File | Field | Before | After |
|---|---|---|---|
StaticMapCache.java |
_caches |
HashMap |
ConcurrentHashMap |
CartHolder.java |
carts |
HashMap |
ConcurrentHashMap |
OdooServiceFactory.java |
service map | HashMap |
ConcurrentHashMap |
PropertyList.java |
properties | HashMap |
ConcurrentHashMap |
5. Volatile Singleton Fields¶
Problem¶
Singleton fields using double-checked locking were missing the volatile keyword. Without volatile, the JVM may reorder writes such that one thread sees a partially constructed object.
Solution¶
Added volatile to all singleton instance fields:
| File | Field |
|---|---|
NTSServiceFactory.java |
instance |
CartHolder.java |
instance |
TlinqEntityFactory.java |
instance |
AppConfig.java |
instance |
ClientConfig.java |
instance |
OdooClientConfig.java |
instance |
JSON.java |
gson |
6. Thread-Safe DateFormat¶
Problem¶
SimpleDateFormat instances were shared across threads. SimpleDateFormat is NOT thread-safe — concurrent parse() or format() calls corrupt internal state.
Solution¶
Replaced shared SimpleDateFormat with ThreadLocal<SimpleDateFormat>:
// Before (unsafe)
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd");
// After (safe)
private static final ThreadLocal<SimpleDateFormat> DATE_FORMAT =
ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));
// Usage: DATE_FORMAT.get().format(date);
| File | Change |
|---|---|
JSON.java (tqamds) |
All date format instances made ThreadLocal |
7. ThreadLocalRandom¶
Problem¶
Math.random() uses a shared Random instance with contention under concurrent access.
Solution¶
Replaced with ThreadLocalRandom.current().nextInt() / .nextDouble() where applicable.
8. AtomicBoolean Guards Against Concurrent Refresh¶
Problem¶
Scheduled data refresh tasks (GoGlobal, Rayna, Tiqets) had no guard against concurrent execution. If a refresh took longer than the interval, two refresh cycles could run simultaneously.
Solution¶
Added AtomicBoolean flag to prevent overlap:
private static final AtomicBoolean refreshing = new AtomicBoolean(false);
public void runRefresh() {
if (!refreshing.compareAndSet(false, true)) {
logger.info("Refresh already in progress — skipping");
return;
}
try {
doRefresh();
} finally {
refreshing.set(false);
}
}
| File | Guard |
|---|---|
GGRefreshRunner.java (tqgglbl) |
GoGlobal refresh |
SDRefreshRunner.java (tqryb2b) |
Rayna B2B refresh |
TiqetsPlugin.java (tqtiqets) |
Tiqets catalog refresh |
9. Daemon Threads for Scheduled Executors¶
Problem¶
ScheduledExecutorService threads were non-daemon, preventing JVM shutdown when the main thread exits.
Solution¶
Used ThreadFactory with setDaemon(true):
ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(r -> {
Thread t = new Thread(r, "goglobal-refresh");
t.setDaemon(true);
return t;
});
| File | Thread Name |
|---|---|
GoGlobalPlugin.java |
goglobal-refresh |
RaynaB2BActPlugin.java |
rayna-refresh |
10. Shared HTTP Client Instances¶
Problem¶
New HTTP client objects were created per request, wasting resources and preventing connection reuse.
Solution¶
Created shared, reusable client instances:
| File | Change |
|---|---|
GoGlobalSoapClient.java |
Shared HttpClient instance |
TiqetsHttpClient.java |
Shared HttpClient instance |
11. Copy-on-Write Cache Refresh¶
Problem¶
TiqetsCacheManager modified live cache maps during refresh. Readers accessing the cache during refresh could see incomplete data.
Solution¶
Build new maps in local variables, then atomically swap the reference:
// Build new cache in local variables
Map<String, TiqetsProduct> newProducts = new HashMap<>();
Map<String, TiqetsExperience> newExperiences = new HashMap<>();
// ... populate from DB ...
// Atomic swap (volatile reference)
this.productsCache = newProducts;
this.experiencesCache = newExperiences;
12. Atomic API Roles Reload¶
Problem¶
ApiRoleManager reloaded role mappings by clearing and repopulating the map. During the window between clear and repopulate, all authorization checks would fail.
Solution¶
Build a new Properties object, then atomically replace the reference:
Properties newRoles = new Properties();
newRoles.load(inputStream);
// Atomic swap
this.roles = newRoles;
13. Synchronized Critical Sections¶
Problem¶
Several business objects accessed shared mutable state without synchronization.
Solution¶
Added synchronized blocks to protect critical sections:
| File | Protected Operation |
|---|---|
CCachedCart.java |
Cart item list modifications |
CTransientCart.java |
Cart item list modifications |
CartFacade.java |
Payment creation (replaced by distributed lock in cluster mode) |
BookingRequestFacade.java |
Booking confirmation (replaced by distributed lock in cluster mode) |
OdooServiceFactory.java |
Service initialization |
14. RaynaCacheManager Refreshing Flag Removal¶
Problem¶
RaynaCacheManager used a plain boolean refreshing flag that was not thread-safe and was redundant given the distributed locking and AtomicBoolean guard.
Solution¶
Removed the flag. Refresh coordination now uses the distributed lock (cluster mode) or AtomicBoolean guard (local mode).
15. Static Final Constants¶
Problem¶
Constant values (strings, numbers, configuration keys) were declared as instance fields or non-final statics, creating unnecessary per-instance overhead and mutation risk.
Solution¶
Converted to static final constants across ~30 files. Examples:
// Before
private String LOCK_KEY = "GoGlobalRefresh";
// After
private static final String LOCK_KEY = "GoGlobalRefresh";
16. Static Final Logger Fields¶
Problem¶
Logger instances were created as instance fields (private Logger logger = ...), creating a new logger reference per object instance.
Solution¶
Changed to private static final Logger logger = ... across all affected files.
17. Defensive Copy of Properties¶
Problem¶
Properties objects were shared by reference, allowing callers to mutate configuration state.
Solution¶
Return defensive copies where properties are exposed to external callers.
18. Odoo userId Made Volatile¶
Problem¶
OdooServiceFactory.userId was set lazily on first use and read by multiple threads without synchronization.
Solution¶
Made the field volatile to ensure visibility across threads.
19. Bug Fix: Reference Comparison (== vs .equals())¶
Problem¶
EntityFacade compared entity action strings using == instead of .equals(), which would fail for dynamically constructed strings.
Solution¶
// Before (broken for non-interned strings)
if (action == "create") { ... }
// After (correct)
if ("create".equals(action)) { ... }
20. Immutable List from Arrays.asList Fixed¶
Problem¶
Arrays.asList() returns a fixed-size list that throws UnsupportedOperationException on add() or remove(). Code in EntityFacade was adding elements to such lists.
Solution¶
Wrapped in new ArrayList<>(Arrays.asList(...)) to create a mutable copy.
21. SQL Injection Prevention¶
Problem¶
NTSCruiseService constructed HQL queries by string concatenation with user-provided values.
Solution¶
Replaced with parameterized queries:
// Before (vulnerable)
String hql = "FROM CruiseEntity WHERE name = '" + name + "'";
// After (safe)
String hql = "FROM CruiseEntity WHERE name = :name";
query.setParameter("name", name);
22. StaticMapCache getEntry Graceful Handling¶
Problem¶
StaticMapCache.getEntry() could throw NullPointerException if the cache was invalidated between the null check and the map access.
Solution¶
Added null-safe access with graceful fallback.
23. Dead Code Removal¶
Problem¶
Unused methods and fields accumulated across the codebase.
Solution¶
Removed dead code:
| File | Removed |
|---|---|
NTSHotelFacade.java |
Unused methods |
24. Double-Confirmation Prevention for Bookings¶
Problem¶
BookingRequestFacade could process the same booking confirmation twice if two concurrent requests arrived (e.g., user double-clicks, or load balancer retry).
Solution¶
After acquiring the distributed lock, reload the booking status. If already confirmed, return the existing confirmation without making a duplicate supplier call:
boolean acquired = lockMap.tryLock("booking-confirm-" + bkReqId, 15, TimeUnit.SECONDS);
if (acquired) {
try {
// Reload status after lock acquisition
BookingRequest reloaded = loadBookingRequest(bkReqId);
if (reloaded.isConfirmed()) {
return reloaded; // Already confirmed
}
// Proceed with supplier confirmation
confirmWithSupplier(reloaded);
} finally {
lockMap.unlock("booking-confirm-" + bkReqId);
}
}
Files Modified Summary¶
By Module¶
| Module | Files | Key Changes |
|---|---|---|
| tqcommon | 8 | Volatile singletons, ConcurrentHashMap, EntityFacade bug fixes |
| tqapp | 20 | Session leak fixes, transaction rollback, synchronized sections, API roles |
| tqapi | 4 | Authentication filter, server initialization, correlation ID |
| tqamds | 5 | ThreadLocal DateFormat, session leak fixes, volatile singleton |
| tqryb2b | 6 | AtomicBoolean guard, daemon threads, session leak fixes |
| tqtiqets | 5 | Copy-on-write cache, AtomicBoolean guard, distributed invalidation |
| tqgglbl | 5 | AtomicBoolean guard, daemon threads, shared HTTP client |
| tqodoo | 3 | Volatile fields, ConcurrentHashMap, synchronized init |
Impact Summary¶
| Category | Count | Severity |
|---|---|---|
| Resource leaks (session/connection) | ~50 methods | Critical |
| Thread-safety violations | ~20 instances | High |
| Concurrency bugs (data corruption) | ~10 instances | High |
| Security vulnerabilities (SQL injection) | 1 instance | Critical |
| Logic bugs (==, immutable lists) | 2 instances | High |
| Performance improvements | ~8 instances | Medium |
| Code quality (constants, dead code) | ~30 files | Low |