You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@causeway.apache.org by da...@apache.org on 2024/06/12 11:05:56 UTC

(causeway) 04/04: CAUSEWAY-3770: makes access to enlistedPropertyChangeRecordsById thread-safe

This is an automated email from the ASF dual-hosted git repository.

danhaywood pushed a commit to branch CAUSEWAY-3770
in repository https://gitbox.apache.org/repos/asf/causeway.git

commit d5cfa7c1aaca2661ec23b7cabaa750593f5c14ae
Author: Andi Huber <ah...@apache.org>
AuthorDate: Tue Jun 11 07:34:59 2024 +0200

    CAUSEWAY-3770: makes access to enlistedPropertyChangeRecordsById
    thread-safe
---
 .../changetracking/EntityChangeTrackerDefault.java | 45 +++++++++++++---------
 1 file changed, 26 insertions(+), 19 deletions(-)

diff --git a/persistence/commons/src/main/java/org/apache/causeway/persistence/commons/integration/changetracking/EntityChangeTrackerDefault.java b/persistence/commons/src/main/java/org/apache/causeway/persistence/commons/integration/changetracking/EntityChangeTrackerDefault.java
index bc76d5ee9c..61d84d16cb 100644
--- a/persistence/commons/src/main/java/org/apache/causeway/persistence/commons/integration/changetracking/EntityChangeTrackerDefault.java
+++ b/persistence/commons/src/main/java/org/apache/causeway/persistence/commons/integration/changetracking/EntityChangeTrackerDefault.java
@@ -158,14 +158,6 @@ implements
         persistentChangesEncountered.set(false);
     }
 
-    private void suppressAutoFlushIfRequired(final Runnable runnable) {
-        if (isSuppressAutoFlush) {
-            FlushMgmt.suppressAutoFlush(runnable);
-        } else {
-            runnable.run();
-        }
-    }
-
     Set<PropertyChangeRecord> snapshotPropertyChangeRecords() {
         // this code path has side-effects, it locks the result for this transaction,
         // such that cannot enlist on top of it
@@ -183,7 +175,7 @@ implements
      * For any enlisted Object Properties collects those, that are meant for publishing,
      * then clears enlisted objects.
      *
-     * @implNote set a lock on the {@code enlistedPropertyChangeRecordsById} {@link Map} until its drained and cleared
+     * @implNote sets a lock on the {@code enlistedPropertyChangeRecordsById} {@link Map} until its drained and cleared
      */
     private Set<PropertyChangeRecord> capturePostValuesAndDrain() {
         synchronized (enlistedPropertyChangeRecordsById) {
@@ -345,11 +337,11 @@ implements
             return;
         }
 
-        suppressAutoFlushIfRequired(() -> {
-            log.debug("enlist entity's property changes for publishing {}", entity);
+        log.debug("enlist entity's property changes for publishing {}", entity);
+
+        runThreadsafeAndSuppressAutoFlushIfRequired(() -> {
             enlistForChangeKindPublishing(entity, EntityChangeKind.CREATE);
 
-            //FIXME[3770] make thread-safe
             MmEntityUtils.streamPropertyChangeRecordIdsForChangePublishing(entity)
                 .filter(pcrId -> ! enlistedPropertyChangeRecordsById.containsKey(pcrId)) // only if not previously seen
                 .forEach(pcrId -> enlistedPropertyChangeRecordsById.put(pcrId, PropertyChangeRecord.ofNew(pcrId)));
@@ -369,7 +361,7 @@ implements
 
         log.debug("enlist entity's property changes for publishing {}", entity);
 
-        suppressAutoFlushIfRequired(() -> {
+        runThreadsafeAndSuppressAutoFlushIfRequired(() -> {
             // we call this come what may;
             // additional properties may now have been changed, and the changeKind for publishing might also be modified
             enlistForChangeKindPublishing(entity, EntityChangeKind.UPDATE);
@@ -382,13 +374,11 @@ implements
                 // provided by ORM
                 ormPropertyChangeRecords
                     .stream()
-                    //FIXME[3770] make thread-safe
                     .filter(pcr -> ! enlistedPropertyChangeRecordsById.containsKey(pcr.getId())) // only if not previously seen
                     .forEach(pcr -> enlistedPropertyChangeRecordsById.put(pcr.getId(), pcr));
             } else {
                 // home-grown approach
                 MmEntityUtils.streamPropertyChangeRecordIdsForChangePublishing(entity)
-                    //FIXME[3770] make thread-safe
                     .filter(pcrId -> ! enlistedPropertyChangeRecordsById.containsKey(pcrId)) // only if not previously seen
                     .map(pcrId -> enlistedPropertyChangeRecordsById.put(pcrId, PropertyChangeRecord.ofCurrent(pcrId)))
                     .filter(Objects::nonNull)   // shouldn't happen, just keeping compiler happy
@@ -402,11 +392,9 @@ implements
 
         _Xray.enlistDeleting(entity, interactionProviderProvider);
 
-        if (isEntityExcludedForChangePublishing(entity)) {
-            return;
-        }
+        if (isEntityExcludedForChangePublishing(entity)) return;
 
-        suppressAutoFlushIfRequired(() -> {
+        runThreadsafeAndSuppressAutoFlushIfRequired(() -> {
             final boolean enlisted = enlistForChangeKindPublishing(entity, EntityChangeKind.DELETE);
             if(enlisted) {
                 log.debug("enlist entity's property changes for publishing {}", entity);
@@ -448,4 +436,23 @@ implements
         }
     }
 
+    /**
+     * @implNote sets a lock on the {@code enlistedPropertyChangeRecordsById} {@link Map}
+     *      until given {@code runnable} completes<p>
+     *      Note: Java supports reentrant locks,
+     *      which allow a thread to acquire the same lock multiple times without deadlocking itself.
+     *      Reentrant locks maintain a count of the number of times a thread has acquired the lock
+     *      and ensure that the lock is released only when the thread exits the synchronized block
+     *      or method the same number of times it entered it.
+     */
+    private void runThreadsafeAndSuppressAutoFlushIfRequired(final Runnable runnable) {
+        synchronized (enlistedPropertyChangeRecordsById) {
+            if (isSuppressAutoFlush) {
+                FlushMgmt.suppressAutoFlush(runnable);
+            } else {
+                runnable.run();
+            }
+        }
+    }
+
 }