Technology Blogs by SAP
Learn how to extend and personalize SAP applications. Follow the SAP technology blog for insights into SAP BTP, ABAP, SAP Analytics Cloud, SAP HANA, and more.
cancel
Showing results for 
Search instead for 
Did you mean: 

Have you ever encountered a “PcdInconsistentUpdateException”? Would you like to know why it is thrown? How can it be resolved? or even better - how can it be prevented? :???:

This blog explains all about it, and also helps Enterprise Portal developers,who are already familiar with the Portal Content Directory concept and APIs, when implementing code.

Starting from NetWeaver Portal 7.1, some of the Portal content Directory (PCD) architecture was changed, with the addition of support for JTA transactions.

The granularity of the handled PCD objects within a transaction is a PCD Unit. For example changing 2 different Pages that reside within the same Role, are actually working on the same Role Unit.

How does the flow work?

While working with a transaction, each Unit that is being modified is held as a cached copy in the transaction.

Before committing the changes, there is a check that the Unit was not modified by anyone else during that time, and an updated version already exists in the DB.

This can happen either from another thread on the same server node or from another server node.

If there is a newer version in the DB, the following exception is thrown: “PcdInconsistentUpdateException”, preventing from inconsistencies in the DB, and the new change is not committed.

When implementing code, even if there is no explicit usage of transactions from application level, every change in the PCD is automatically wrapped with a transaction.


If you encounter such an exception in the default trace file, you can check what application is responsible for the exception by looking at the stack trace of the exception. If this application is provided by SAP, search for SAP notes, as a fix might be already available.


More information can be found in the following SAP Knowledge Base Article:

1752506 - PcdInconsistentUpdateException in the DefaultTrace - What it Means and How to Troubleshoo...


Despite the new architecture, there are still cases in which PCD inconsistencies occur in versions NW7.1 and above.

In order to detect and remove PCD inconsistencies in those versions, please refer to SAP note:

1473054 - Checking database consistency for the PCD for CE 7.1 & above


In older Enterprise Portal versions (such as NW7.0) where there was no prevention at the PCD layer, there is also a tool that can be used to clear those inconsistencies. It is documented in SAP note:

1086644 - Checking database consistency for the PCD


The notes described must be used only by Portal experts, as they describe actions that should be executed on the PCD tables. Those actions may corrupt the portal if performed in a wrong way.


Below you can find code snippets that demonstrate how to prevent the aforementioned issues.


There are two main mechanisms that should be used:


1. Locking - you should lock the object before you start changing it, in order to help protecting the current object from concurrent changes. The locking is done on a Unit object. When one application has acquired a lock, another application will fail to do so when trying to change the object at the same time; the same happens if the same application runs in parallel. If the application failed perform locking, it should handle it accordingly. (for example - if it has User Interface, then displaying a message to the screen might be a good option).


There are two types of Lock Handlers:

PCD layer (com.sapportals.portal.pcd.gl): ILockHandle ( <--  link to the javadoc documentation)

PCM layer (com.sap.portal.pcm.admin): ILockHandle ( <-- link to the javadoc documentation)


The ILockHandle that should be used in accordance with the operations performed - If you are using PCM or Semantics APIs in the administration environment, then the PCM lock handler should be used, whereas the PCD lock handler is usually used when objects do not exist in the PCD yet (descriptor objects that need to be bound to the PCD), or other PCD API usages (like enqueue lock usages for PCD changes).


2. Transaction - using the transaction mechanism, you can make sure that the change is committed only if all the modifications were done successfully. Using transaction mechanism also causes changes that are constructed from a few modifications to become atomic, meaning that if one of the modifications failed, all the previous modifications are not committed (i.e. rollback is performed). Hence it keeps the objects in the PCD in a consistent state.


The following transaction manager class should be used:

TxManager <-- Click on the link to read the full explanation about the transaction manager, and the different methods that it provides.

You will find the rest of the relevant APIs/classes in the same package com.sap.transaction.* .

* read about the correct usage, e.g. the differences between required() and requiresNew()

Example:

import java.util.Hashtable;

import javax.naming.Context;

import javax.naming.InitialContext;

import javax.naming.NamingException;

import com.sap.portal.directory.Constants;

import com.sap.portal.pcm.admin.IAdminBase;

import com.sap.portal.pcm.admin.ILockHandle;

import com.sap.portal.pcm.admin.PcmConstants;

import com.sap.portal.pcm.admin.ILockHandle.LockInfo;

import com.sap.security.api.IUser;

import com.sap.tc.logging.Location;

import com.sap.tc.logging.Severity;

import com.sap.transaction.TransactionTicket;

import com.sap.transaction.TxDemarcationException;

import com.sap.transaction.TxException;

import com.sap.transaction.TxManager;

import com.sap.transaction.TxRollbackException;

import com.sapportals.portal.pcd.gl.IPcdContext;

import com.sapportals.portal.prt.util.Guid;

public void performPcdOperation (IUser user, String pcdPath) throws NamingException {


Location location = Location.getLocation(<class name>.class);


IAdminBase adminBaseObj = null;

TransactionTicket txTicket = null;

ILockHandle lockHandle = null;

String lockId = null;

String userId = user.getUniqueID();

try {

     Hashtable<String, Object> env = new Hashtable<String, Object>();

     env.put(Context.INITIAL_CONTEXT_FACTORY, IPcdContext.PCD_INITIAL_CONTEXT_FACTORY);

     env.put(Context.SECURITY_PRINCIPAL, user);

     env.put(Constants.REQUESTED_ASPECT, PcmConstants.ASPECT_ADMINISTRATION);

     InitialContext initCtx = new InitialContext(env);

     adminBaseObj = (IAdminBase) initCtx.lookup(pcdPath);

    //In this example, generates unique lock ID using com.sapportals.portal.prt.util.Guid

     //It can also be an identifier such as session ID. Useful when wanting to distinguish between sessions

     //or to check whether it is the same lock - in case we want to use the already generated one

     //(if all is considered inside one flow)

     lockId = (new Guid()).toString();

     lockHandle = (ILockHandle)adminBaseObj.getImplementation(IAdminBase.LOCK_HANDLE);

     if (lockHandle == null) {

          location.errorT("lockHandle is not available!!! This is a problem!");

          return;

     }

     LockInfo lockInfo = lockHandle.getLockInfo();

    //if NULL, then the object is not locked

     if (lockInfo == null) {

          boolean isLocked = lockHandle.setLock(userId, lockId, "Lock for binding remote objects to role");

          //returns true if the lock could be acquired or if the object was already locked for the same user and lockId;

          //false if the object cannot be locked, because it is already locked for a different user or lockId.

          if (isLocked) {

               txTicket = TxManager.required();

               // Perform the desired changes. For example:

               updateAllChangesUnderTheLockedUnitObj(adminBaseObj);

               TxManager.commitLevel(txTicket);

          }

     } else {

          location.errorT("lockHandle already locked!");

     }

} catch (TxRollbackException e) {

     location.traceThrowableT(Severity.ERROR, "Transaction error during execution", e);

} catch (TxException e) {

     location.traceThrowableT(Severity.ERROR, "Transaction error during execution", e);

} catch (NamingException e) {

     try {

          TxManager.setRollbackOnly();

     } catch (TxException e1) {

          location.traceThrowableT(Severity.ERROR, "Transaction error during execution of setRollbackOnly()", e1);

     }

     throw e;

} finally {

     if (txTicket != null) {

          try {

               TxManager.leaveLevel(txTicket);

          } catch (TxDemarcationException e) {

               location.traceThrowableT(Severity.ERROR, "Transaction error during leaveLevel() execution", e);

          } catch (TxRollbackException e) {

               location.traceThrowableT(Severity.ERROR, "Transaction error during leaveLevel() execution", e);

          } catch (TxException e) {

               location.traceThrowableT(Severity.ERROR, "Transaction error during leaveLevel() execution", e);

          }

     }    

     if (lockHandle != null) {

          lockHandle.releaseLock(userId, lockId);

     }

}

} //End of method


Some things to bear in mind when implementing using the above APIs:

1. Avoid from performing transactions on operations that cause inconsistencies between the DB and the memory like clearing caches and PCD publishChanges (as they are still not committed….).

2. When changing personalized content / content in the UCD, instead of locking you should find why there is parallel writing, as the same user is not supposed to run in parallel - might be a bad application design (like putting all the personalized data inside one big unit).

3. Consider whether it is better to first lock or first start with the transaction (can change according to application logic or importance).

1 Comment