Singleton

Introduction
A Global Singleton is a class of which only one instance exists within a program. This information originally posted here and subsequently here.

Background
In Caché, each job runs in a self-contained execution environment (i.e. a separate process instead of a thread). However, it is possible for each process to share data through multidimensional storage, but it is the responsibility of the application developer to ensure read and write access to global variables is properly co-ordinated (or 'synchronized') between processes to prevent concurrency problems. Also, Caché defines any global variable whose name starts with 'CacheTemp' as being temporary, which means changes are not usually written to disk and are instead maintained within the in-memory buffer pool.

Class Definitions
Class User.Stock Extends (Singleton, %XML.Adaptor) {

Property Products As array Of Product;

}

Class User.Product Extends (%SerialObject, %XML.Adaptor) {

Property ProductId As %String;

Property ProductName As %String(MAXLEN = "");

}

/// The Singleton class represents a global singleton object that can /// be instantiated by multiple processes. The 'Get' class method is used to obtain /// an in-memory object reference and the 'Set' method is used to save any changes to /// state. See below for an example. /// ///  /// Set one=##class(Singleton).Get(,.sc) /// Set one.GlobalProperty="Some Value" /// Set sc=one.Set ///  /// /// This class can also be extended. Class User.Singleton Extends %SerialObject {

/// Refer to About Concurrency for more details /// on the optional pConcurrency argument. ClassMethod Get(pConcurrency As %Integer = -1, Output pStatus As %Status = {$$$OK}) As Singleton [ Final ] {	// check if singleton object already instantiated Set oRef = "" For { Set oRef = $ZObjNext(oRef) If oRef = "" Quit If oRef.%ClassName(1) = ..%ClassName(1) Quit }	If $IsObject(oRef) Quit oRef // determine what lock needs to be applied If '$IsValidNum(pConcurrency, 0, -1, 4) { Set pStatus = $$$ERROR($$$LockTypeInvalid, pConcurrency) Quit $$$NULLOREF }	If pConcurrency = -1 Set pConcurrency = $Xecute("Quit "_..#DEFAULTCONCURRENCY) // acquire lock for global singleton object Set lockTO = $ZUtil(115,4), lockOK = 1 If pConcurrency<4, pConcurrency { Lock +^CacheTempUser("Singleton", ..%ClassName(1))#"S":lockTO Set lockOK = $Test } ElseIf pConcurrency = 4 { Lock +^CacheTempUser("Singleton", ..%ClassName(1)):lockTO Set lockOK = $Test }	If 'lockOK { If pConcurrency = 4 { Set pStatus = $$$ERROR($$$LockFailedToAcquireExclusive, ..%ClassName(1)) } Else { Set pStatus = $$$ERROR($$$LockFailedToAcquireRead, ..%ClassName(1)) }		Quit $$$NULLOREF }	// retrieve global singleton object and deserialise Set oId = $Get(^CacheTempUser("Singleton", ..%ClassName(1))) Set oRef = ..%Open(oId) //,, .pStatus)	If '$IsObject(oRef) Set pStatus = $$$ERROR($$$GeneralError, "Failed to load singleton object.")	// release temporary lock	If (pConcurrency = 1) || (pConcurrency = 2) {		Lock -^CacheTempUser("Singleton", ..%ClassName(1))#"S"	}	// singleton object failed to load	If $$$ISERR(pStatus) {		// release retained lock		If pConcurrency = 3 {			Lock -^CacheTempUser("Singleton", ..%ClassName(1))#"S"		}		If pConcurrency = 4 {			Lock -^CacheTempUser("Singleton", ..%ClassName(1))		}		Quit $$$NULLOREF	}	// store concurrency state and return in-memory object reference	Set oRef.Concurrency = pConcurrency	Quit oRef }

Method Set As %Status [ Final ] {	// check for version change Set oId0 = $Get(^CacheTempUser("Singleton", ..%ClassName(1))) Set oRef0 = ..%Open(oId0) //,, .sc)	If '$IsObject(oRef0) Quit $$$ERROR($$$GeneralError, "Failed to load singleton object.")	If oRef0.Version = ..Version {		Set ..Version = ..Version + 1	} Else {		Quit $$$ERROR($$$ConcurrencyVersionMismatch, ..%ClassName(1))	}			// serialise local singleton object and check status code	Set sc = ..%GetSwizzleObject(,.oId) If $$$ISERR(sc) Quit sc	// acquire exclusive lock on global singleton object	Set lockTO = $ZUtil(115,4)	Lock +^CacheTempUser("Singleton", ..%ClassName(1)):lockTO	If '$Test Quit $$$ERROR($$$LockFailedToAcquireExclusive, ..%ClassName(1))	// update global singleton object and release lock	Set ^CacheTempUser("Singleton", ..%ClassName(1)) = oId	Lock -^CacheTempUser("Singleton", ..%ClassName(1))	Quit $$$OK }

Method %OnNew As %Status [ Final, Internal ] {	// do not allow constructor method to be called Quit $$$ERROR($$$GeneralError, "Can't instantiate directly.") }

Method %OnConstructClone As %Status [ Final, Internal ] {	// do not allow singleton object to be cloned Quit $$$ERROR($$$GeneralError, "Can't clone instance.") }

Method %OnClose As %Status [ Final, Internal ] {	// reference count for singleton object is now zero, so	// release lock on global singleton object, if applicable If ..Concurrency = 3 Lock -^CacheTempUser("Singleton", ..%ClassName(1))#"S" If ..Concurrency = 4 Lock -^CacheTempUser("Singleton", ..%ClassName(1)) Quit $$$OK }

Property Concurrency As %Integer [ Final, Private, Transient ];

Property Version As %Integer [ Final, Private ];

}

Usage
Process 1

USER>Set stock=##class(Stock).Get USER>Set product=##class(Product).%New USER>Set product.ProductId="0201633612" USER>Set product.ProductName="Design patterns: elements of reusable object-oriented software" USER>Do stock.Products.SetAt(product, product.ProductId) USER>Set sc=stock.Set USER>Write stock.Products.Count 1

Process 2

USER>Set stock=##class(Stock).Get USER>Set product=##class(Product).%New USER>Set product.ProductId="0201485672" USER>Set product.ProductName="Refactoring: Improving the Design of Existing Code" USER>Do stock.Products.SetAt(product, product.ProductId) USER>Set sc=stock.Set USER>Write stock.Products.Count 2