Closing MboSets

November 22, 2021

Introduction

Automation Scripts were introduced as a way to provide custom business logic without creating custom Java classes. This was presented as a way to get the benefits of custom code without customizing Maximo, in this way Automation Scripts were a type of configuration. However, Automation Scripts provide full access to the Java API and if implemented without careful consideration can lead to memory leaks, poor performance and even system crashes.

I have found that many people are not aware that interacting with MboSets in Automation Scripts can open database connections that if not properly closed can lead to resources never being freed.

In this post we will look at ensuring that MboSets and other resources are properly closed so connections are freed up and memory is not leaked.

The examples and discussion below are for situations where a MboSet is obtained independently from the implicit mbo or mboset variables. Do not close the implicit mbo or mboset, as they are being managed by the Maximo framework and cleaning up or closing them will result in unexpected and likely undesired behavior.

Working with MboSets

MboSets are, as the name indicates, sets of Maximo Business Objects (Mbo) that are roughly equivalent to accessing a table and rows in the database. Underlying the MboSet is a JDBC ResultSet object that has an associated database connection and resources. The connection and resource allocation is associated with a user session and will eventually be cleaned up when the user session is terminated. However, it is important to note that the System user session never terminates.

I have seen many cases where developers use the UserInfo object for the System user when getting a MboSet. The code generally looks something like following.

MXServer = Java.type("psdi.server.MXServer");
var mboSet = service.getMboSet("WORKORDER", MXServer.getMXServer().getSystemUserInfo());
// perform operations with the MboSet.

Closing MboSets

First, because the connection and set are associated with the System user and the session never terminates all records loaded into the set will remain in memory until the JVM is restarted. The associated database connections will also be maintained and Maximo will not reclaim those sessions into the session pool. Whenever possible you should avoid using the System user, both for the reason that serious leaks can occur, but also using the System user means there are no security constraints and this opens the door for unexpected privilege escalation.

Even if you are using the implicit userInfo variable to maintain the mbo within the current user's session, to ensure that these connections are released you need to call close() on the MboSet when you are done. Rewriting our previous example, it now looks like this.

MXServer = Java.type("psdi.server.MXServer");
var mboSet = service.getMboSet("WORKORDER", MXServer.getMXServer().getSystemUserInfo());
// perform operations with the MboSet.
mboSet.close();

By calling close() we tell Maximo to also close the underlying JDBC ResultSet, Statement, and then free the Connection back to the Maximo managed connection pool. This ensures that all the database related resources are released.

Cleaning Up MboSets

In addition to database resources a MboSet may have a record lock to prevent other users from editing a record, may be part of existing transactions, and may be referenced by an owner MboSet. To ensure that locks are released, transactions cleared and related MboSets dereferenced, the cleanup() method should be called. Since these actions all deal with in memory internal references, I typically call cleanup() before calling close.

Rewriting the example to include cleanup(), it now looks like this.

MXServer = Java.type("psdi.server.MXServer");
var mboSet = service.getMboSet("WORKORDER", MXServer.getMXServer().getSystemUserInfo());
// perform operations with the MboSet.
mboSet.cleanup();
mboSet.close();

Finally the Finally

Our previous example is great and the MboSet is cleaned up and closed at the end of our script, but what happens if an error occurs and the block exits unexpectedly? In the case of an error, the script terminates and the cleanup() and close() methods are never called. This is where the try and finally statement comes into play.

By wrapping the MboSet actions in a try finally statement we can put the cleanup() and close() methods in the finally block and ensure that even if an unhandled error occurs, the finally block will be called before the script exits.

Below is the rewritten example, note that we are wrapping the cleanup() and close() methods in their own try catch structures to ensure that even if an error occurs when trying to clean up or close the MboSet, both are called.

When using a try finally the MboSet variable must be declared outside the try block so that it is still in scope for the finally block.

// define the MboSet outside the try/finally so it is available in the finally block
var mboSet;
try {
mboSet = service.getMboSet("WORKORDER", userInfo);
// perform operations with the MboSet.
} finally {
try {
// release any pending Maximo transactions.
mboSet.cleanup();
} catch(error) {
// log the error, but there is nothing we can do to respond to it.
service.log_error(error);
}
try {
// remove any references and close the underlying JDBC ResultSet.
mboSet.close();
} catch(error) {
// log the error, but there is nothing we can do to respond to it.
service.log_error(error);
}
}

Final Example

What we have done so far is functional, but can be restructured to be safer and easier to reuse. In the example below the script now makes use of functions and checks types to ensure that we are working with a valid MboSet.

MboSet = Java.type("psdi.mbo.MboSet");
main();
function main() {
// define the MboSet outside the try/finally so it is available in the finally block
var mboSet;
try {
mboSet = service.getMboSet("WORKORDER", userInfo);
// perform operations with the MboSet.
} finally {
_close(mboSet);
}
}
// use the _ in the name to indicate that this is an internal script function.
function _close(mboSet) {
// make sure the provided MboSet is defined and is an instance of psdi.mbo.MboSet
if (typeof mboSet !== 'undefined' && mboSet instanceof MboSet) {
try {
// release any pending Maximo transactions.
mboSet.cleanup();
} catch(error) {
// log the error, but there is nothing we can do to respond to it.
service.log_error(error);
}
try {
// remove any references and close the underlying JDBC ResultSet.
mboSet.close();
} catch(error) {
// log the error, but there is nothing we can do to respond to it.
service.log_error(error);
}
}
}

Conclusion

When working with MboSet references that are not provided implicitly by the Automation Script framework, it is very important to clean up and close references.

In addition, MboSets that are obtained using the System user live forever and are never automatically cleaned up because the session never terminates. For this reason and because of other security concerns, using the System user to get a MboSet should be avoided if possible.

Finally, to make sure cleanup() and close() are always called, wrap the MboSet accessing code in a try finally statement so that even if an unexpected error occurs, your MboSet references will be properly cleaned up and closed.

In the time it took you to read this blog post...

You could have deployed Opqo, our game-changing mobile solution for Maximo.

Opqo is simple to acquire, simple to deploy and simple to use, with clear transparent monthly pricing that is flexible to your usage.