Hi @Dmitry Maslennikov ,

Yesterday I experienced this kind of problem with web socket.
To limit the license usage, I did this : 

Class dc.journalindexer.WebSocket Extends %CSP.WebSocket

Method OnPreServer() As %Status
    Do %session.Login("wsuser",,1)
    Set ..SharedConnection = 1
    ; ... some lines of code
    Quit sc

Method OnClientMessage(
	data As %String = "",
	close As %Integer) As %Status
    ; force logout and EndSession on disconnect from client
    If $Get(close) = 1 {
        Do %session.Logout()        
        Set %session.EndSession = 1
        Do ..EndServer()

	Quit 0

Hello @Michael Davidovich ,

Did you try to override OnPreDispatch ?

Also you can configure your web application to use a subclass of %CSP.SessionEvents and override OnStartRequest, OnEndRequest .

Set Application = "/csp/YourWebApp"
Do ##class(Security.Applications).Get(Application, .p)
Set p("EventClass") = "YourCSPEventsClassName"
Set sc = ##class(Security.Applications).Modify(Application, .p)

Hi @Scott Roth ,

Ten years ago, I implement %ZSTOP routine to execute code when a job process exits or when the instance shutdown.  I removed my code, but the routine looks like this: 

%ZSTOP ; User shutdown routine. 
SYSTEM ; Cache stopping
	Set oldNs = $Namespace
	Try {
		Set $Namespace = "MYAPPNAMESPACE"
		; Do something when stopping the instance.
	}Catch(ex) {}
	Set $Namespace = oldNS
LOGIN ; a user logs out of Cache (user account or telnet)
JOB ; JOB'd process exits. 
	If $Namespace = "MYAPPNAMESPACE" {
		; do something when job process exits
CALLIN ; process exits via CALLIN interface. 
Logit(entrypoint, caller) PRIVATE ;

Hello @Pietro Montorfano 

You can try to write your own export method.
Using %Library.RoutineIndex looks good.  Example to export all ".MAC" :

set tRes = ##class(%SQL.Statement).%ExecDirect(.tStmt,"select name||'.'||type as itemName  from %Library.RoutineIndex where type = ? and $Extract(name,1) <> '%'","MAC")
While tRes.%Next() {
	Do $SYSTEM.OBJ.ExportUDL(tRes.%Get("itemName"),"<dir>/"_tRes.%Get("itemName"))

For adding a member, this is the IP of the first member (primary).  It's correct.
No matter the virtual IP, it's the role of the arbiter\agent to communicate who is primary in case of a switch.  

However, a virtual IP is convenient for access to your applications.

For example, with web applications: If the system switches from node A to node B, it is more convenient to use a virtual IP address.  You can use this in your web server configuration so that it always points to the primary node.


Two years ago, I analyzed the behaviours using stream and %Persistent class.  

Class Test.Stream1 Extends %Persistent

Property st As %GlobalBinaryStream;

ClassMethod add1() As %Status
	; write stream in ^Test.Stream1S
	Set o = ..%New()
	Do o.st.Write("azeruiop")
	Quit o.%Save()

ClassMethod add2() As %Status
	; write stream in ^CacheStream
	Set o = ..%New()
	Set st = ##class(%GlobalBinaryStream).%New()
	Do st.Write("azeruiop")
	Set o.st = st
	Quit o.%Save()

Storage Default
<Data name="Stream1DefaultData">
<Value name="1">
<Value name="2">


The method add2 use ^CacheStream due to the default storage usage (as described by @Robert Cemper ).  

However, We can force the storage in ^Test.Stream1S with :
Do st.%LocationSet("^Test.Stream1S")

Hi @Robert Cemper, @Vitaliy.Serdtsev 

Thank you for your replies!
I found a solution to do this without any change to an existing code, not simple but It works and could be useful in a critical situation.

I read the code of ^%ETN and see these lines :

UserError() s $zt="UserErrorE"
 i $d(^rOBJ("%ZERROR")) d 
 . n %00000 d ^%ZERROR

So, If we create a "%ZERROR" routine, we have an entry point :


    If $Data(^zForceCommit($Job)) { ; to avoid do this for all processes...
        While $TLEVEL {


And then, we must terminate the process like that:

Set pid = "1234" ; pid to terminate
Set ^zForceCommit(pid)=""
Zn "%SYS"
Set process = ##class(SYS.Process).%OpenId(pid)
Set sc = process.Terminate(1)

It's important to use the SYS.Process class and the Terminate method with argument 1 to use ^%ETN.

Code snipet to expose the problem.

Class ZUser.NewClass1 Extends %Persistent [ Not ProcedureBlock ]

ClassMethod Demo()

    Do ..TestLock()
    ; This class is Not ProcedureBlock the record still locked

    ; If the class is ProcedureBlock the record is released.
    ; try by yourself :-)

ClassMethod TestLock() As %Status
    Set id = $Order(^ZUser.NewClass1D(""))
    If id = "" {
        Set obj = ##class(ZUser.NewClass1).%New()
        Do obj.%Save()
        Kill obj

    Set id = $Order(^ZUser.NewClass1D(""))

    Set obj = ##class(ZUser.NewClass1).%OpenId(id, 4)

    ; in case of usage Not ProcedureBlock you should 
    ; kill obj or set obj="" (and all others variables with this object reference) to release the lock

    Return $$$OK