Skip to content

fix: Keep MSession/MRole cache in sync within each JVM#600

Merged
yamelsenih merged 1 commit into
solop-develop:developfrom
EdwinBetanc0urt:bugfix/msession-mrole-cache-coherence
Jun 2, 2026
Merged

fix: Keep MSession/MRole cache in sync within each JVM#600
yamelsenih merged 1 commit into
solop-develop:developfrom
EdwinBetanc0urt:bugfix/msession-mrole-cache-coherence

Conversation

@EdwinBetanc0urt
Copy link
Copy Markdown
Member

When two JVMs share the same database (e.g. adempiere-grpc-server + adempiere-report-engine-service), their in-memory caches diverge after writes performed by one of them: the second JVM keeps serving the previous state until TTL. This is most visible after change-role — reports run with the wrong tenant/org filter and return empty.

Changes

  • MSession.afterSave(...): new override that refreshes s_sessions with the current instance on every successful save. Keeps the cache aligned with the row's actual state (including Processed) without an extra DB hit on read. Processed sessions stay visible — callers that need to reject them (e.g. KeycloakSessionHandler.loadExistingSessionContext) check isProcessed() themselves; callers that need them (audit log, changelog, keepalive on closing sessions) still get the row.
  • MSession.logout(): dropped the redundant explicit s_sessions.remove(...)afterSave already refreshes the entry after setProcessed(true) + saveEx().
  • MRole.removeFromCache(int roleId, int userId): new static helper to evict a (role, user) cache entry so the next get() rebuilds the access SQL. Required when the user's effective tenant/org changes.

Test

Login as System → change role to a company → run a report whose query goes through MRole.addAccessSQL. SQL filter must contain the company's AD_Client_ID / AD_Org_ID, not 0, 0. Full cross-JVM coverage still requires a distributed cache (out of scope here).

When two JVMs share the same database (e.g. `adempiere-grpc-server` + `adempiere-report-engine-service`), their in-memory caches diverge after writes performed by one of them: the second JVM keeps serving the previous state until TTL. This is most visible after `change-role` — reports run with the wrong tenant/org filter and return empty.

## Changes

- `MSession.afterSave(...)`: new override that refreshes `s_sessions` with the current instance on every successful save. Keeps the cache aligned with the row's actual state (including `Processed`) without an extra DB hit on read. Processed sessions stay visible — callers that need to reject them (e.g. `KeycloakSessionHandler.loadExistingSessionContext`) check `isProcessed()` themselves; callers that need them (audit log, changelog, keepalive on closing sessions) still get the row.
- `MSession.logout()`: dropped the redundant explicit `s_sessions.remove(...)` — `afterSave` already refreshes the entry after `setProcessed(true)` + `saveEx()`.
- `MRole.removeFromCache(int roleId, int userId)`: new static helper to evict a `(role, user)` cache entry so the next `get()` rebuilds the access SQL. Required when the user's effective tenant/org changes.

## Test

Login as System → change role to a company → run a report whose query goes through `MRole.addAccessSQL`. SQL filter must contain the company's `AD_Client_ID` / `AD_Org_ID`, not `0, 0`. Full cross-JVM coverage still requires a distributed cache (out of scope here).
@yamelsenih yamelsenih merged commit 5483bad into solop-develop:develop Jun 2, 2026
2 checks passed
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.

2 participants