On synchronization in XPages

The biggest issue that we had with XPages in 8.5.0 was performance, every piece of code you wrote had to be optimised to get the best performance, something which us Notes developers have spent all too little time on in the past.

With 8.5.1 everything is faster from DDE onwards, but there are some code changes that we can take advantage of to make things even better. Most important of these is synchronization. For those of you who know Java this will be a familiar concept that has just been implemented into Server Side JavaScript (SSJS). But for the LotusScript developer, here is my quick attempt at explaining it…

If you have a commonly used piece of code, it will be called by multiple different areas of the system at the same time (or at least very close together) and return the same result. If you’re doing some expensive piece of work (such as a DbLookup) then all of the calls to that code will run and there will be an inevitable performance hit.

By adding the synchronize wrapper around the “expensive” code what will happen is that all of the calls to that code will queue up behind each other, so that code can only run once at a time. We can then cache the results of the code so that all of the queued up calls can just get that result from memory (the applicationScope for example) rather than having to go off and calculate it again and again. A very simple concept but one which saves a huge amount of processing time.

But what will our code look like? Well here is a sample function from the upcoming IQJam application that will be launching later this week.

function getControlPanelFieldString(fieldname){

synchronized(applicationScope){

if(isCacheInvalid(“controlpanel_” + fieldname, 600)){

var controlPanels = database.getView(“lookupControlPanel”);

var controlPanel = controlPanels.getFirstDocument();

applicationScope.put(“controlpanel_” + fieldname, controlPanel.getItemValueString(fieldname));

controlPanel = null;

controlPanels = null;

}

  return applicationScope.get(“controlpanel_” + fieldname);

}

/**

A generic caching mechanism for each key will check to see if it is ‘n’ seconds

since it was last updated. Use for things that change relatively infrequently  

*/

function isCacheInvalid(key, cacheInterval){

var currentTime = new Date().getTime();

if (!applicationScope.containsKey(key + “_time”)){

applicationScope.put(key + “_time”, currentTime);

  return true;

}

var diffInSecs = Math.ceil((currentTime – applicationScope.get(key + “_time”)) / 1000);

if (diffInSecs < cacheInterval) {

return false;

} else {

applicationScope.put(key + “_time”, currentTime);

return true;

}

}

We store lots of tiny variables about the application in a “Control Panel” document and then read them as needed into the applicationScope. The nature of the variables is that they don’t change much so we can cache them for long periods of time (10 minutes in this case).

As with lots of XPages code, the idea for this came from the Discussion template (which is quite dramatically different under the covers in 8.5.1), so I’d highly recommend digging through the code in there to get an idea of what you can do with XPages. And of course we have to offer thanks to Thomas Gumz and the other XPages developers in IBM who write the code that the rest of us can then re-use for our own dastardly ends.

Disclaimer: Notes/Domino 8.5.1 is beta software and no features are guaranteed until release.

Join the Conversation

4 Comments

  1. Hi Matt,

    I tried to create a simple sequence number generator but no matter what object I synch on it seems a synchronize block cannot handle multiple threads properly.I tested it with the following code:

    var viewSeq:NotesView = database.getView("seq");var docSeq:NotesDocument = viewSeq.getFirstDocument();

    for (i = 0; i < 100; i++) { synchronized(applicationScope) { viewSeq.refresh(); newId = @Right("00000" + @Text((@TextToNumber(docSeq.getItemValueString("seqNumber")) + 1)), 5); docSeq.replaceItemValue("seqNumber", newId); docSeq.save(); } }

    I use applicationScope as the lock, because in theory there is only one applicationScope object in any app.I expected this code to generate sequential numbers correctly, but when we tried to test it with 2 users who clicked on a button at the same time the last generated number was between 100 and 200.I tried it with a synchronized java function and it works as expected.

    Did I miss something here?Is it possible to define synchronized functions?

    Thanks,Tibor

    Like

  2. Quick update on myself:I didn’t know but it’s a bad practice to use a synchronized block inside a for loop (my knowledge about threads is far from desirable 🙂 ).Another thing is that though the applicationScope object seems like a singleton it’s still doesn’t block correctly, so if I use a simple singleton object then it works fine:

    var viewSeq:NotesView = database.getView("seq");var docSeq:NotesDocument = viewSeq.getFirstDocument(); synchronized(util.TestSingleton.getInstance()) { viewSeq.refresh(); for (i = 0; i < 100; i++) { newId = @Right("00000" + @Text((@TextToNumber(docSeq.getItemValueString("seqNumber")) + 1)), 5); docSeq.replaceItemValue("seqNumber", newId); docSeq.save(); } }

    Like

  3. Am I late to the party? 🙂 seams so….

    regarding the isCacheInvalid function, why not use the last modified date/time of the settings/control panel document? and keep the cache as long as the document is not changed.

    Like

Leave a comment