Skip to content

Implement thread-safe session registry and REST endpoints for Drools sessions (O3-5064)#2

Closed
UjjawalPrabhat wants to merge 6 commits into
openmrs:mainfrom
UjjawalPrabhat:feature/O3-5064-session-registry
Closed

Implement thread-safe session registry and REST endpoints for Drools sessions (O3-5064)#2
UjjawalPrabhat wants to merge 6 commits into
openmrs:mainfrom
UjjawalPrabhat:feature/O3-5064-session-registry

Conversation

@UjjawalPrabhat

@UjjawalPrabhat UjjawalPrabhat commented Oct 6, 2025

Copy link
Copy Markdown

Description

This PR implements the solution for OpenMRS issue O3-5064 by reintroducing a thread-safe session registry for Drools KieSession instances and exposing new REST endpoints to query and manage active sessions. All changes maintain backward compatibility and leverage Drools 6+ native thread safety.

Key Changes

  1. New Interface & Implementation
  • Added ThreadSafeSessionRegistry (API)
  • Added ThreadSafeSessionRegistryImpl with ConcurrentHashMap storage, AtomicLong counters, and automatic cleanup
  • Added SessionMetadata class to track session creation, access times, and reuse flags
  1. Session Executor Enhancements
  • Autowired ThreadSafeSessionRegistry into DroolsSessionExecutor
  • Increased executor thread pool size from 5 to 10
  • Added executeAgainstExistingSession() and executeSessionWithReuse() methods to reuse auto-startable sessions
  1. REST Controller Enhancements
  • Autowired ThreadSafeSessionRegistry into DroolsSessionController
  • Removed duplicate executeSession() invocation bug
  • Exposed five new endpoints under /ws/rest/v1/drools/session to list sessions, fetch metadata, execute rules, dispose sessions, and check existence
  1. Engine Service Integration
  • Autowired ThreadSafeSessionRegistry into DroolsEngineServiceImpl
  • Auto-register auto-startable sessions in requestSession() after session creation
  1. Tests & Documentation
  • Added ThreadSafeSessionRegistryTest suite (23 tests)
  • All existing and new tests pass (45/45)
  • Updated implementation guide and quick-reference markdown
  1. Verification
  • mvn clean compile and mvn test succeed with zero failures
  • New REST endpoints return correct responses for session listing, metadata, execution, disposal, and existence checks
  • Thread safety validated under concurrent access scenarios
  • Backward compatibility with existing APIs confirmed

This PR fulfills the O3-5064 requirement by restoring REST-based access to Drools sessions in a safe, performant, and maintainable manner.

Features:
- ThreadSafeSessionRegistry with ConcurrentHashMap
- 5 new REST endpoints for session management
- Auto-startable session support
- Session metadata tracking and cleanup
- Fixed async execution bug in DroolsSessionController
- Backward compatible implementation
@gracepotma

Copy link
Copy Markdown
Contributor

Thanks so much @UjjawalPrabhat for your contribution! Exciting to see someone else working in the new-ish drools repo. Tagged some experts for review :)

@ibacher ibacher left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks @UjjawalPrabhat. I think it would better if the overall solution worked a little bit more closely to how the module already works.

I also want to point out that thread-safety is not just a matter of having underlying synchronized data stores. Thread-safety here means that operations in one thread should not be able to interfere with operations in another thread. Usually this means ensuring that only a single-thread can work with a "safely" retrieved instance at a time.

Comment on lines -137 to -141
// register resources
if (ruleProvider.getRuleResources() != null) {
ruleProvider.getRuleResources().forEach(kieContainerBuilder::addResource);
}
// register session configs

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why are you deleting comments?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Why are you deleting comments?

You're absolutely right. Those comments provide valuable context for understanding the code flow. They were accidentally removed during refactoring. I'll restore all deleted comments.

Comment on lines +35 to +37
@Autowired
private ThreadSafeSessionRegistry sessionRegistry;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why is this being autowired here?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Why is this being autowired here?

Initially I thought this was needed for auto-start session registration, but you're correct that this creates unnecessary coupling. After reviewing the auto-start workflow, the registration should happen in DroolsEngineRunner instead, where auto-startable sessions are actually initialized. I'll remove this autowiring.

Comment thread api/src/main/java/org/openmrs/module/drools/api/impl/DroolsEngineServiceImpl.java Outdated
@ibacher

ibacher commented Oct 6, 2025

Copy link
Copy Markdown
Member

Also, please review our pull request tips which despite its name actually contains rules for how we expect PRs to be put together.

@UjjawalPrabhat

Copy link
Copy Markdown
Author

Thank you @ibacher for the comprehensive review! After analyzing all your feedback, I see that several comments point to fundamental architectural issues that require a complete revision:

Core Issues Requiring Architectural Changes:

  1. Thread Safety Design Flaw: You're absolutely right that using ConcurrentHashMap doesn't make the class thread-safe. The real issue is preventing Thread1 from accessing a session while Thread2 disposes it.

  2. Missing Check-out Mechanism: The key insight from your feedback is that we need a "check-out" pattern to provide exclusive access to sessions, not just retrieval.

  3. Auto-start Logic Error: The registration in requestSession() would indeed cause IllegalArgumentException in production since auto-startable sessions should already be running.

  4. REST Naming Convention: I should follow the existing "rules" terminology for consumer-facing APIs.

  5. Exception Handling: The warn + throw pattern is inconsistent - should return boolean success indicator instead.

Proposed Solution:
I'll implement a session check-out mechanism with SessionLease and per-session locking:

try (SessionLease lease = registry.checkOutSession(sessionId, timeout)) {
KieSession session = lease.getSession();
// Exclusive access guaranteed
return executeRules(session, params);
} // Auto-return via try-with-resources

This addresses the Thread1/Thread2 data race scenario you highlighted and provides true thread safety.

I'll create a revised implementation addressing all these architectural concerns. Would you prefer I close this PR and create a new one, or update this one with the fixes?

Thanks for the thorough review!

Architectural fixes based on maintainer feedback:

✅ Added SessionLease with per-session locking for exclusive access
✅ Moved auto-start registration to DroolsEngineRunner.run() startup phase
✅ Renamed REST endpoints from /session to /rules per OpenMRS conventions
✅ Changed registerSession() to return boolean instead of throwing
✅ Added Scheduled cleanup with consistent debug logging
✅ Updated usage patterns with try-with-resources pattern
✅ Comprehensive test coverage including concurrency scenarios

Prevents Thread1/Thread2 data race issues through check-out mechanism.
All 47 tests passing.

Addresses all architectural concerns from code review.
@UjjawalPrabhat UjjawalPrabhat force-pushed the feature/O3-5064-session-registry branch from cd10e10 to a090476 Compare October 7, 2025 15:21
- Restored accidentally deleted comments in registerRuleProvider()
- Adjusted cleanup logging levels per maintainer clarification
- Addresses final maintainer feedback items
@UjjawalPrabhat UjjawalPrabhat force-pushed the feature/O3-5064-session-registry branch from a090476 to 2a46b7d Compare October 7, 2025 15:22
@UjjawalPrabhat

Copy link
Copy Markdown
Author

Thank you @ibacher for the thorough review. All requested changes are now implemented:

  • Comments Restored: Added back // register resources and // register session configs in registerRuleProvider().
  • Cleanup Logging: Uses log.info when sessions are removed, log.debug otherwise.
  • Thread Safety: SessionLease with per-session locking prevents data races.
  • Auto-start Logic: Moved registration to DroolsEngineRunner.run() startup phase.
  • REST Naming: Renamed /session endpoints to /rules (maintained /rule/{id} for backward compatibility).
  • Exception Handling: registerSession() returns boolean, removing warn+throw anti-pattern.
  • Scheduled Cleanup: Enabled Scheduled hourly cleanup.
  • Tests: 47 tests passing, including concurrency scenarios.

Ready for re-review!

@samuelmale samuelmale left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thank you for working on this @UjjawalPrabhat! My overall concern is scope creep and over-engineering. O3-5064 is basically about implementing a simple registry, which makes it possible to do something like:

  if (config.isAutoStart()) {
       session = registry.get(sessionId);  // retrieve from registry
   } else {
       session = createNewSession();       // create, use, dispose
   }

All auto-startable sessions get created and started upon module startup; these are tracked in a session registry which are later accessed on demand in a thread-safe fashion.

If possible, let's come up with the simplest approach to achieve the above. We can later implement concepts like Session Pooling, Scheduled cleanups, etc.

Comment thread api/src/main/java/org/openmrs/module/drools/api/impl/DroolsEngineServiceImpl.java Outdated
Comment on lines +56 to +57
// Note: Registration moved to DroolsEngineRunner.run() for auto-startable sessions
// This method now only creates sessions without registering them

@samuelmale samuelmale Oct 13, 2025

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think this method should invoke the registry if necessary, ie, if the requested session is auto-startable, we pick the session from the registry.

Comment thread api/src/main/java/org/openmrs/module/drools/session/SessionMetadata.java Outdated
* @param sessionId the session identifier
* @return SimpleObject with existence status
*/
@RequestMapping(value = "/rules/{sessionId}/exists", method = RequestMethod.GET)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I like the idea of supporting an endpoint used to check if there is an existing session configuration of a specific ID, but I wouldn't restrict this to the registry.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Do we really need an /exists endpoint? It seems more in line with REST to just GET /rules/{sessionId}, potentially even HEAD /rules/{sessionId} which should respond with a 200 if the sessionId exists and a 404 if it does not.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

So currently, if a sessionId doesn't exist, the backend will throw an exception (500 response code):

Screenshot 2025-10-14 at 13 23 29

We should maybe be more explicit and respond with a 404 instead of a 500?

@UjjawalPrabhat

Copy link
Copy Markdown
Author

Thank you @samuelmale for the detailed feedback! I completely understand the concern about scope creep and over-engineering. You're absolutely right that the core requirement is a simple registry for auto-startable sessions.

I got carried away implementing a comprehensive session management system when the ticket really just needs:

if (config.isAutoStart()) {
session = registry.get(sessionId); // reuse from registry
} else {
session = createNewSession(); // create fresh
}

Clarifying Questions Before Simplification:

  1. Registry Location: Should I move the registry back to be a private field in DroolsEngineServiceImpl instead of a separate autowired component?

  2. Thread Safety: Should I keep the SessionLease check-out mechanism that @ibacher requested, or simplify to just get(sessionId) with basic synchronization?

  3. Auto-start Timing: Should auto-startable sessions be registered during requestSession() calls, or still at module startup?

  4. REST Endpoints: Which specific endpoints should I keep? Just the existing /rule/{sessionId} plus maybe a disposal endpoint?

  5. Error Handling: For the 404 vs 500 issue @ibacher mentioned - should I add proper error handling for non-existent sessions?

Proposed Simplified Approach:

  • Simple SessionRegistry interface with just: get(), put(), remove(), contains()
  • Registry private to DroolsEngineServiceImpl
  • Auto-startable logic in requestSession() method
  • Remove SessionMetadata complexity
  • Minimal REST changes
  • Keep basic thread safety

Happy to implement the simplified version once I have clarity on the above points!

@gracepotma gracepotma requested a review from samuelmale October 27, 2025 15:21
@samuelmale

Copy link
Copy Markdown
Member

Should the registry live exclusively inside DroolsEngineServiceImpl rather than be a standalone component?

The registry should be a standalone internal component interfaced via the service.

For thread safety, do we keep the SessionLease lock-based check-out, or simplify to a basic get(sessionId)?

You've already gone over this with @ibacher, so let's keep it!

Where should auto-start registration occur – during requestSession() or at module startup?

The logic for auto-starting (registration) a session can be abstracted by the service, but the entry point for an auto-started session is at module startup.

Which REST endpoints should remain in scope (e.g. just /rule/{sessionId} and maybe a delete)?

For errors on unknown session IDs, should we return a 404 (or HEAD) instead of 500?

I think 4 and 5 can be handled in a separate PR.

- Removed: SessionMetadata, scheduled cleanup, new REST endpoints
- Kept: SessionLease check-out pattern, per-session locking, auto-start registration
- Tests: 16/16 passing
@UjjawalPrabhat

Copy link
Copy Markdown
Author

Thank you @samuelmale for the clarifications! I've simplified the implementation to focus on core registry functionality only.

Changes Made:

Removed (Scope Reduction):

  • SessionMetadata class and all metadata tracking
  • All new REST endpoints (deferred to separate PR per feedback)
  • Scheduled cleanup (can be added later if needed)
  • Controller direct access to registry
  • Session statistics and access tracking

Kept (Core Requirements):

  • SessionRegistry interface (renamed from ThreadSafeSessionRegistry)
  • SessionLease check-out mechanism (approved by @ibacher)
  • Per-session ReentrantLock for thread safety
  • Auto-start registration at module startup (DroolsEngineRunner)
  • Basic operations: register, checkOut, sessionExists

Architecture:

The registry now enables the simple pattern you requested:
if (config.isAutoStart()) {
try (SessionLease lease = registry.checkOutSession(sessionId, timeout, unit)) {
session = lease.getSession();
// Use session with exclusive lock
} // Lock auto-released
} else {
session = createNewSession();
}

Testing:

  • 16 tests passing in SessionRegistryTest
  • Clean build with no errors

Ready for final review!

Comment thread omod/src/main/java/org/openmrs/module/drools/DroolsEngineRunner.java Outdated
UjjawalPrabhat and others added 2 commits November 11, 2025 20:42
- Add service wrapper methods for registry operations
- Remove direct registry access from DroolsEngineRunner
- Registry now private to DroolsEngineService
- All tests passing
@UjjawalPrabhat UjjawalPrabhat marked this pull request as draft January 15, 2026 21:21
@UjjawalPrabhat UjjawalPrabhat deleted the feature/O3-5064-session-registry branch January 16, 2026 21:25
@UjjawalPrabhat UjjawalPrabhat restored the feature/O3-5064-session-registry branch January 16, 2026 21:29
@UjjawalPrabhat UjjawalPrabhat deleted the feature/O3-5064-session-registry branch January 16, 2026 21:33
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.

4 participants