03-17-2021 04:51 PM
Hello,
TLDR: Within a specific transaction, is it possible that multiple events will run concurrently, or are all events garanteed to run synchronously?
The longer version:
We are writing a behavior that will send node details to AWS Event Bridge on every edit for integration with another system. In order to handle the fact that multiple nodes may be edited within a transaction and each node may be altered several times due to folder rules and other behaviors, we are binding to EVERY_EVENT and keeping a collection of the edited nodes which is bound to the transaction with AlfrescoTransactionSupport.bindResource().
This was the example we are working off of:
https://angelborroy.wordpress.com/2015/05/22/alfresco-implementing-delete-behavior/
I originally assumed that Angel knows what he's talking about so it must be safe to use a plain old ArrayList and not worring about locking or race conditions, but I'd like to be 100% positive because it is hard to find these kind of errors in testing.
Thanks,
Chris
03-18-2021 05:27 PM
I am not sure if you are going the right way. Angel's example creates new transactions and does processing there, so it is asynchronous and can run in parallel. That code is executed after original modification was commited (starting from line 52 - afterCommit means what it says).
While under heavy load, we ran into issues and had to modify asynchronous processing little bit with counter (I am adding only a part of the code):
if (transactionCounter == null) { transactionCounter = 0; } // Bind listener to current transaction AlfrescoTransactionSupport.bindListener(new DocumentTransactionListener(transactionCounter)); LOGGER.trace("Listener is binded to current nodeRef processing transaction. (transactionCounter = " + transactionCounter + ")"); // Put resources to be used in transaction listener TransactionSupportUtil.bindResource(KEY_RELATED_NODE + transactionCounter.toString(), nodeRef); TransactionSupportUtil.bindResource(KEY_PROPS_BEFORE + transactionCounter.toString(), before); TransactionSupportUtil.bindResource(KEY_PROPS_AFTER + transactionCounter.toString(), after); TransactionSupportUtil.bindResource(KEY_DOCUMENT + transactionCounter.toString(), notInScanning); transactionCounter++; return null; } }; AuthenticationUtil.runAs(raw, AuthenticationUtil.getAdminUserName()); } // Listening "afterCommit" transaction event private class DocumentTransactionListener extends TransactionListenerAdapter implements TransactionListener { private final Integer id; public DocumentTransactionListener(final Integer id) { super(); this.id = id; } @Override public void afterCommit() { final NodeRef nodeRef = TransactionSupportUtil.getResource(KEY_RELATED_NODE + id.toString()); final Map<QName, Serializable> before = TransactionSupportUtil.getResource(KEY_PROPS_BEFORE + id.toString()); final Map<QName, Serializable> after = TransactionSupportUtil.getResource(KEY_PROPS_AFTER + id.toString()); final Boolean notInScanning = TransactionSupportUtil.getResource(KEY_DOCUMENT + id.toString()); LOGGER.trace("DocumentTransactionListener afterCommit... NodeRef: " + nodeRef);
03-18-2021 05:40 PM
Be aware that synchronous behaviors are not guaranteed to execute in any specified order. So you can get unexpected results and also weird exceptions. And if something fails, whole transaction is rolled back. In our case we were trying to move document created via CMIS within the transaction and it was failing. Therefore we changed to asycnhronous afterCommit. CMIS client receives success, transaction completes (document gets created) and then we move document to the destination folder in the new transaction.
03-18-2021 06:37 PM
Thanks for the reply!
Our main concern, and the reason we want to know if events run synchronously, is whether we need to be concerned about race conditions and thread safety when using AlfrescoTransactionSupport.getResource() and AlfrescoTransactionSupport.bindResource().
Our current plan is to create a collection to track all of the nodes that were edited in the transaction, much the same way that Angel used an array to collected related nodes. Then, when the transaction is done, we will use that collection to report out, to an external queue, the final state of each node edited in that transaction. We are going down this path because we can never know in the event handler whether this is the final event for a given node or not, and we don't want the overhead of doing external IO on every event.
The concern is that if these events run asynchronously, then there is no garantee that two separate events won't call getResource at about the same time and get a null value, and then call bindResource with a new collection so that the last one in wins and the first one is just lost.
I can see how your transactionCounter method will let you find the last event fired without worring about the concurrency issues I just described, but I don't think it will handle the situation where there are multiple nodes edited within a ransaction... at least as far as I understand it now.
03-18-2021 07:46 PM
Take also a look at using/extending auditing: https://docs.alfresco.com/content-services/community/admin/audit/
03-19-2021 09:28 AM
I had no idea such an API existed, that may be a better solution. Thanks for the advice!
Explore our Alfresco products with the links below. Use labels to filter content by product module.