├── .gitignore ├── cls └── IAT │ └── PubSub │ ├── Example │ ├── SubDummy.cls │ ├── SubPatient.cls │ ├── MsgOrder.cls │ ├── MsgPatient.cls │ ├── MsgDummy.cls │ └── Main.cls │ ├── Publisher.cls │ ├── MsgBody.cls │ ├── MsgHeader.cls │ ├── Subscriber.cls │ └── Manager.cls ├── LICENSE ├── inc └── IAT.PubSub.INC └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | *.bak 2 | -------------------------------------------------------------------------------- /cls/IAT/PubSub/Example/SubDummy.cls: -------------------------------------------------------------------------------- 1 | /// Subscriber for Patient channel 2 | Class IAT.PubSub.Example.SubDummy Extends IAT.PubSub.Subscriber 3 | { 4 | 5 | /// Callback that is called when a message is received by subscriber 6 | Method OnMessage(pBody As IAT.PubSub.Example.MsgDummy) As %Status 7 | { 8 | set ret = $$$OK 9 | try { 10 | $$$LOG("MsgDummy. ID="_pBody.%Id()_" Text="_pBody.Text) 11 | } catch ex { 12 | set ret = ex.AsStatus() 13 | } 14 | quit ret 15 | } 16 | 17 | } 18 | 19 | -------------------------------------------------------------------------------- /cls/IAT/PubSub/Example/SubPatient.cls: -------------------------------------------------------------------------------- 1 | /// Subscriber for Patient channel 2 | Class IAT.PubSub.Example.SubPatient Extends IAT.PubSub.Subscriber 3 | { 4 | 5 | /// Callback that is called when a message is received by subscriber 6 | Method OnMessage(pBody As IAT.PubSub.Example.MsgPatient) As %Status 7 | { 8 | set ret = $$$OK 9 | try { 10 | $$$LOG("MsgPatient. ID="_pBody.%Id()_" Name="_pBody.Name) 11 | } catch ex { 12 | set ret = ex.AsStatus() 13 | } 14 | quit ret 15 | } 16 | 17 | } 18 | 19 | -------------------------------------------------------------------------------- /cls/IAT/PubSub/Example/MsgOrder.cls: -------------------------------------------------------------------------------- 1 | Class IAT.PubSub.Example.MsgOrder Extends IAT.PubSub.MsgBody 2 | { 3 | 4 | Property Number As %String; 5 | 6 | Property Description As %String; 7 | 8 | Storage Default 9 | { 10 | 11 | "MsgOrder" 12 | 13 | Number 14 | 15 | 16 | Description 17 | 18 | 19 | MsgOrderDefaultData 20 | %Library.CacheStorage 21 | } 22 | 23 | } 24 | 25 | -------------------------------------------------------------------------------- /cls/IAT/PubSub/Publisher.cls: -------------------------------------------------------------------------------- 1 | Include IAT.PubSub 2 | 3 | /// Simple generic publisher 4 | Class IAT.PubSub.Publisher Extends %RegisteredObject 5 | { 6 | 7 | /// Send a message body to a channel 8 | ClassMethod Send(pChannel As %String, pBody As MsgBody) As %Status 9 | { 10 | set ret = $$$OK 11 | try { 12 | set header = ##class(MsgHeader).%New(pBody) 13 | $$$ThrowOnError(header.%Save()) 14 | set id = header.%Id() 15 | 16 | set $$$Queue(pChannel,id)="" 17 | do $system.Event.Signal($$$ResourceName(pChannel),id) 18 | } catch ex { 19 | set ret = ex.AsStatus() 20 | } 21 | quit ret 22 | } 23 | 24 | } 25 | 26 | -------------------------------------------------------------------------------- /cls/IAT/PubSub/MsgBody.cls: -------------------------------------------------------------------------------- 1 | /// Generic message body for Publisher-Subscriber system. 2 | /// All messages MUST extend from this class. 3 | Class IAT.PubSub.MsgBody Extends %Persistent 4 | { 5 | 6 | Storage Default 7 | { 8 | 9 | 10 | %%CLASSNAME 11 | 12 | 13 | ^IAT.PubSub.MsgBodyD 14 | MsgBodyDefaultData 15 | ^IAT.PubSub.MsgBodyD 16 | ^IAT.PubSub.MsgBodyI 17 | ^IAT.PubSub.MsgBodyS 18 | %Library.CacheStorage 19 | } 20 | 21 | } 22 | 23 | -------------------------------------------------------------------------------- /cls/IAT/PubSub/Example/MsgPatient.cls: -------------------------------------------------------------------------------- 1 | /// Example of message used for Patient channel 2 | Class IAT.PubSub.Example.MsgPatient Extends IAT.PubSub.MsgBody 3 | { 4 | 5 | Property MRN As %String; 6 | 7 | Property Name As %String; 8 | 9 | Property LastName As %String; 10 | 11 | Property DOB As %String; 12 | 13 | Storage Default 14 | { 15 | 16 | "MsgPatient" 17 | 18 | MRN 19 | 20 | 21 | Name 22 | 23 | 24 | LastName 25 | 26 | 27 | DOB 28 | 29 | 30 | MsgPatientDefaultData 31 | %Library.CacheStorage 32 | } 33 | 34 | } 35 | 36 | -------------------------------------------------------------------------------- /cls/IAT/PubSub/Example/MsgDummy.cls: -------------------------------------------------------------------------------- 1 | /// Example of message used for Patient channel 2 | Class IAT.PubSub.Example.MsgDummy Extends IAT.PubSub.MsgBody 3 | { 4 | 5 | Property Username As %String [ InitialExpression = {$username} ]; 6 | 7 | Property Timestamp As %TimeStamp [ InitialExpression = {$zdatetime($horolog,3)} ]; 8 | 9 | Property Text As %String(MAXLEN = 500); 10 | 11 | Storage Default 12 | { 13 | 14 | "MsgDummy" 15 | 16 | Username 17 | 18 | 19 | Timestamp 20 | 21 | 22 | Text 23 | 24 | 25 | MsgDummyDefaultData 26 | %Library.CacheStorage 27 | } 28 | 29 | } 30 | 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 InterSystems Iberia 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /inc/IAT.PubSub.INC: -------------------------------------------------------------------------------- 1 | ROUTINE IAT.PubSub [Type=INC] 2 | #; Publisher-Subscriber 3 | 4 | #; Utils 5 | #define CurrentClass $classname() 6 | #define CurrentMethod ##safeexpression(""""_$get(%methodname)_"""") 7 | 8 | #; Resource name for a channel (see $system.Event) 9 | #define ResourceName(%channel) "PubSub"_%channel 10 | 11 | #; Configuration: 12 | #; $$$Config(CHANNEL, "Subscriber", SUBSCRIBERSUBCLASS) = NUMBEROFPROCESSES 13 | #define Config ^PubSub.Config 14 | 15 | #; Status of running jobs: 16 | #; $$$StatusJob(JOB, "Channel")=CHANNEL 17 | #; $$$StatusJob(JOB, "Created")=TIMESTAMP 18 | #define StatusJob ^PubSub.StatusJob 19 | 20 | #; Status of running channels: 21 | #; $$$StatusChannel(CHANNEL, "Job", JOB)="" 22 | #define StatusChannel ^PubSub.StatusChannel 23 | 24 | #; Persistent Queue 25 | #; $$$Queue(CHANNEL, HEADERID) 26 | #define Queue ^PubSub.Queue 27 | 28 | #; Traces 29 | #define LOG(%txt) set ^PubSub.Log($i(^PubSub.Log),$$$CurrentClass,$$$CurrentMethod,$job,$ztime($p($h,",",2),1))=%txt 30 | 31 | #define DEBUGMODE 1 32 | #define LOGDEBUG(%txt) ##continue 33 | #if $$$DEBUGMODE ##continue 34 | $$$LOG(%txt) ##continue 35 | #endif 36 | -------------------------------------------------------------------------------- /cls/IAT/PubSub/MsgHeader.cls: -------------------------------------------------------------------------------- 1 | /// Message header for Publisher-Subscriber system. 2 | Class IAT.PubSub.MsgHeader Extends %Persistent 3 | { 4 | 5 | /// Classname of the body referenced by this header. 6 | Property BodyClass As %String(MAXLEN = ""); 7 | 8 | /// ID of the body referenced by this header. 9 | Property BodyId As %String; 10 | 11 | /// Create a MsgHeader from a given MsgBody 12 | Method %OnNew(pBody As MsgBody) As %Status [ Private, ServerOnly = 1 ] 13 | { 14 | set ret = $$$OK 15 | try { 16 | if $isobject(pBody) { 17 | set ..BodyClass = $classname(pBody) 18 | set ..BodyId = pBody.%Id() 19 | } 20 | } catch ex { 21 | set ret = ex.AsStatus() 22 | } 23 | quit ret 24 | } 25 | 26 | Storage Default 27 | { 28 | 29 | 30 | %%CLASSNAME 31 | 32 | 33 | BodyClass 34 | 35 | 36 | BodyId 37 | 38 | 39 | ^IAT.PubSub.MsgHeaderD 40 | MsgHeaderDefaultData 41 | ^IAT.PubSub.MsgHeaderD 42 | ^IAT.PubSub.MsgHeaderI 43 | ^IAT.PubSub.MsgHeaderS 44 | %Library.CacheStorage 45 | } 46 | 47 | } 48 | 49 | -------------------------------------------------------------------------------- /cls/IAT/PubSub/Subscriber.cls: -------------------------------------------------------------------------------- 1 | Include IAT.PubSub 2 | 3 | /// Generic subscriber for Publisher-Subscriber system. 4 | /// All subscriber *MUST* extend from this class. 5 | Class IAT.PubSub.Subscriber Extends %RegisteredObject 6 | { 7 | 8 | /// Subscribed channel 9 | Property Channel As %String; 10 | 11 | /// Resource name for subscribed channel (see $system.Event) 12 | Property Resource As %String; 13 | 14 | /// Header of the message received. 15 | Property Header As MsgHeader; 16 | 17 | /// Start running subscriber. 18 | ClassMethod Start(pChannel As %String) As %Status 19 | { 20 | set ret = $$$OK 21 | try { 22 | $$$LOG(pChannel) 23 | set obj = ..%New(pChannel) 24 | if obj=$$$NULLOREF $$$ThrowStatus(%objlasterror) 25 | do obj.Run() 26 | } catch ex { 27 | set ret = ex.AsStatus() 28 | $$$LOG($system.Status.GetErrorText(ret)) 29 | } 30 | quit ret 31 | } 32 | 33 | /// Create a Subscriber from a given channel. 34 | Method %OnNew(pChannel As %String) As %Status [ Private, ServerOnly = 1 ] 35 | { 36 | set ret = $$$OK 37 | try { 38 | set ..Channel = pChannel 39 | set ..Resource = $$$ResourceName(pChannel) 40 | } catch ex { 41 | set ret = ex.AsStatus() 42 | } 43 | quit ret 44 | } 45 | 46 | /// Subscriber run method. Wait for a message and process it. 47 | /// User *MUST* implement OnMessage callback. 48 | Method Run() As %Status 49 | { 50 | set ret = $$$OK 51 | try { 52 | for { 53 | // wait until a message is received 54 | set data = $system.Event.WaitMsg(..Resource) 55 | 56 | // retrieve data from event 57 | set code = $listget(data,1) 58 | set headerId = $listget(data,2) 59 | 60 | // dequeueu message from queue 61 | kill $$$Queue(..Channel, headerId) 62 | $$$LOG("Grabs: "_headerId) 63 | 64 | // instantiate message header 65 | set ..Header = ##class(MsgHeader).%OpenId(headerId) 66 | $$$LOG(..Header.BodyClass_" "_..Header.BodyId) 67 | 68 | // instantiate message body 69 | set body = $classmethod(..Header.BodyClass, "%OpenId", ..Header.BodyId) 70 | 71 | // execute user callback for given body 72 | $$$ThrowOnError(..OnMessage(body)) 73 | } 74 | } catch ex { 75 | set ret = ex.AsStatus() 76 | $$$LOG($system.Status.GetOneErrorText(ret)) 77 | } 78 | } 79 | 80 | /// Callback that is called when a message is received by subscriber 81 | Method OnMessage(pBody As IAT.PubSub.MsgBody) As %Status 82 | { 83 | quit $$$ERROR($$$NotImplemented) 84 | } 85 | 86 | } 87 | 88 | -------------------------------------------------------------------------------- /cls/IAT/PubSub/Example/Main.cls: -------------------------------------------------------------------------------- 1 | Include IAT.PubSub 2 | 3 | /// Simple test class for Publisher-Subscriber. 4 | ///
  5 | /// ; create events and subscriber jobs
  6 | /// do ##class(IAT.PubSub.Example.Main).Setup()
  7 | /// ; send some messages to channels
  8 | /// do ##class(IAT.PubSub.Example.Main).Run()
  9 | /// ; show log
 10 | /// zw ^PubSub.Log
 11 | /// 
12 | Class IAT.PubSub.Example.Main Extends %RegisteredObject 13 | { 14 | 15 | Parameter ChannelPatient = "Patient"; 16 | 17 | Parameter ChannelDummy = "Dummy"; 18 | 19 | /// Config and setup Publisher-Subscriber 20 | ClassMethod Setup() As %Status 21 | { 22 | set ret = $$$OK 23 | try { 24 | set $$$Config(..#ChannelPatient, "Subscriber", "IAT.PubSub.Example.SubPatient") = 4 25 | set $$$Config(..#ChannelDummy, "Subscriber", "IAT.PubSub.Example.SubDummy") = 2 26 | $$$ThrowOnError(##class(IAT.PubSub.Manager).Setup()) 27 | } catch ex { 28 | set ret = ex.AsStatus() 29 | } 30 | quit ret 31 | } 32 | 33 | /// Delete messages 34 | ClassMethod Delete() As %Status 35 | { 36 | set ret = $$$OK 37 | try { 38 | $$$ThrowOnError(##class(IAT.PubSub.MsgBody).%DeleteExtent()) 39 | $$$ThrowOnError(##class(IAT.PubSub.MsgHeader).%DeleteExtent()) 40 | } catch ex { 41 | set ret = ex.AsStatus() 42 | } 43 | quit ret 44 | } 45 | 46 | /// Run tests. Send some messages channels. 47 | ClassMethod Run(pNum As %Integer = 5) As %Status 48 | { 49 | set ret = $$$OK 50 | try { 51 | $$$ThrowOnError(..SendPatient(pNum)) 52 | $$$ThrowOnError(..SendDummy(pNum)) 53 | } catch ex { 54 | set ret = ex.AsStatus() 55 | $$$LOG($system.Status.GetErrorText(ret)) 56 | } 57 | quit ret 58 | } 59 | 60 | /// Publish messages in patient channel 61 | ClassMethod SendPatient(pNum As %Integer = 5) As %Status 62 | { 63 | set ret = $$$OK 64 | try { 65 | for i=1:1:pNum { 66 | set data = ##class(IAT.PubSub.Example.MsgPatient).%New() 67 | set data.MRN = i 68 | set data.Name = i 69 | set data.LastName = i 70 | $$$ThrowOnError(data.%Save()) 71 | 72 | $$$LOG("Sending data "_i_" to "_..#ChannelPatient) 73 | $$$ThrowOnError(##class(IAT.PubSub.Publisher).Send(..#ChannelPatient, data)) 74 | } 75 | } catch ex { 76 | set ret = ex.AsStatus() 77 | } 78 | quit ret 79 | } 80 | 81 | /// Publish messages in dummy channel 82 | ClassMethod SendDummy(pNum As %Integer = 5) As %Status 83 | { 84 | set ret = $$$OK 85 | try { 86 | for i=1:1:pNum { 87 | set data = ##class(IAT.PubSub.Example.MsgDummy).%New() 88 | set data.Text = i 89 | $$$ThrowOnError(data.%Save()) 90 | 91 | $$$LOG("Sending data "_i_" to "_..#ChannelDummy) 92 | $$$ThrowOnError(##class(IAT.PubSub.Publisher).Send(..#ChannelDummy, data)) 93 | } 94 | } catch ex { 95 | set ret = ex.AsStatus() 96 | } 97 | quit ret 98 | } 99 | 100 | } 101 | 102 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Caché Publisher - Subscriber 2 | 3 | The purpose of this repository is to provide a simple example of *Publisher-Subscriber model* implemented using [InterSystems Caché](http://www.intersystems.com/our-products/cache/cache-overview/). 4 | 5 | This example came out of a session which discussed ways of processing work **asynchronously** using **persistent queues**. 6 | 7 | ## Installation 8 | ``` 9 | set path="C:\Temp\cache-iat-pubsub-master\cache" 10 | do $system.OBJ.ImportDir(path,"*.inc","ck",.error,1) 11 | do $system.OBJ.ImportDir(path,"*.xml","ck",.error,1) 12 | ``` 13 | 14 | ## Run the code 15 | ``` 16 | ; create events and subscriber jobs 17 | do ##class(IAT.S05.PubSub.Example.Main).Setup() 18 | ; send some messages to channels 19 | do ##class(IAT.S05.PubSub.Example.Main).Run() 20 | ; show log 21 | zwrite ^PubSub.Log 22 | ``` 23 | 24 | ## Implementation 25 | [Caché Event API](http://docs.intersystems.com/cache20152/csp/documatic/%25CSP.Documatic.cls?LIBRARY=samples&CLASSNAME=%25SYSTEM.Event&MEMBER=&CSPCHD=001000000000Np0VyNuBwTDT$PiD99hk51sfrKUAanvu8tIi0c&CSPSHARE=1) is used to let the subscriber processes sleep until they are notified. 26 | 27 | ### Main classes 28 | * Manager - handles core operations such as adding subscribers, terminate all subscriber jobs, etc. 29 | * Publisher - generic publisher, provides a basic *Send* operation which notifies a subscriber that a message is ready. 30 | * Subscriber - generic subscriber, all subscriber must extend from this. 31 | ![](https://dl.dropboxusercontent.com/u/2198214/ISC/images/Events_PubSub_Classes1.png) 32 | 33 | ### Example classes 34 | The following classes provides an example of how the main classes could be used, in this case there are two subscribers: 35 | * *SubPatient* which handles Patient related messages. 36 | * *SubDummy* which handles dummy messages. 37 | ![](https://dl.dropboxusercontent.com/u/2198214/ISC/images/Events_PubSub_Classes2.png) 38 | 39 | ### Log 40 | After running the example, the log stored in *^PubSub.Log* global will look similar to this: 41 | ![](https://dl.dropboxusercontent.com/u/2198214/ISC/images/Events_PubSub_Log.png) 42 | 43 | ### Further discussion 44 | There are several points of this example that can be discussed as an exercise: 45 | 46 | #### Caché Event API vs Semaphores 47 | * **$system.Event** (Caché Event API) can only wait / wake up resources on the **same system** (not networking supported). 48 | * It would be interesting implementing this same model using **$system.Semaphore** over an *ECP* scenario. 49 | 50 | #### Multiple types of subscribers 51 | * In *IAT.PubSub.Publisher:Send()* method, it simply creates a message header, save it to queue and send a signal to one of the subscribers. 52 | * It would be interesting allowing more than one type of subscriber per channel. In this case, the *Send()* method should create several message headers and signal the different types of configured subscribers. 53 | 54 | # Developer Community 55 | Have a look at [InterSystems Developer Community](https://community.intersystems.com/) to learn about InterSystems technology, sharing solutions and staying up-to-date on the latest developments. 56 | 57 | This example was published in https://community.intersystems.com/post/simple-systemevent-examples 58 | -------------------------------------------------------------------------------- /cls/IAT/PubSub/Manager.cls: -------------------------------------------------------------------------------- 1 | Include IAT.PubSub 2 | 3 | /// Publisher-Subscriber manager 4 | Class IAT.PubSub.Manager Extends %RegisteredObject 5 | { 6 | 7 | /// Setup Publisher-Subscriber system as specified in $$$Config global. 8 | /// This method can be called periodically to check that jobs are running as configured. 9 | /// Configuration global is expected to be like: 10 | /// $$$Config(CHANNEL, "Subscriber", SUBSCRIBERSUBCLASS) = NUMBEROFPROCESSES 11 | /// CHANNEL = channel name 12 | /// SUBSCRIBERSUBCLASS = full name of the subscriber class (must extend from Subscriber) 13 | /// NUMBEROFPROCESSES = number of processes that will be running subscriber class 14 | ClassMethod Setup() 15 | { 16 | set ret = $$$OK 17 | try { 18 | set channel="" 19 | for { 20 | set channel=$order($$$Config(channel)) 21 | quit:channel="" 22 | 23 | $$$ThrowOnError(..AddChannel(channel)) 24 | } 25 | } catch ex { 26 | set ret = ex.AsStatus() 27 | } 28 | quit ret 29 | } 30 | 31 | /// Add a channel. 32 | /// It must be configured in $$$Config global. 33 | ClassMethod AddChannel(pChannel As %String) As %Status 34 | { 35 | set ret = $$$OK 36 | try { 37 | $$$LOG("channel="_pChannel) 38 | 39 | do $system.Event.Create($$$ResourceName(pChannel)) 40 | set subscriber="" 41 | for { 42 | set subscriber=$order($$$Config(pChannel,"Subscriber",subscriber),1,numJobs) 43 | quit:subscriber="" 44 | 45 | $$$ThrowOnError(..AddSubscriber(pChannel, subscriber, numJobs)) 46 | } 47 | } catch ex { 48 | set ret = ex.AsStatus() 49 | } 50 | quit ret 51 | } 52 | 53 | /// Add a subscriber to a channel with a number of processes. 54 | /// It must be configured in $$$Config global. 55 | ClassMethod AddSubscriber(pChannel As %String, pSubscriber As %String, pNumJobs As %Integer) As %Status 56 | { 57 | set ret = $$$OK 58 | try { 59 | // check dead, running subscriber jobs for channel 60 | set job="", runningJobs=0, deadJobs="" 61 | for { 62 | set job=$order($$$StatusChannel(pChannel, "Job", job)) 63 | quit:job="" 64 | 65 | set process = ##class(%SYS.ProcessQuery).%OpenId(job) 66 | if process'=$$$NULLOREF { 67 | set runningJobs=runningJobs+1 68 | } else { 69 | set deadJobs=deadJobs_$listbuild(job) 70 | } 71 | } 72 | 73 | // clean dead jobs 74 | for i=1:1:$listlength(deadJobs) { 75 | $$$ThrowOnError(..CleanJob($listget(deadJobs,i))) 76 | } 77 | 78 | // create new jobs 79 | set newJobs = (pNumJobs-runningJobs) 80 | $$$LOG(pSubscriber_" config="_pNumJobs_" dead="_$ll(deadJobs)_" running="_runningJobs_" new="_newJobs) 81 | 82 | if newJobs > 0 { 83 | for i=1:1:newJobs { 84 | // start subscriber 85 | job $classmethod(pSubscriber, "Start", pChannel) 86 | 87 | // save job information 88 | set $$$StatusChannel(pChannel, "Job", $zchild)="" 89 | set $$$StatusJob($zchild, "Channel")=pChannel 90 | set $$$StatusJob($zchild, "Created")=$zdatetime($horolog,3) 91 | } 92 | } 93 | } catch ex { 94 | set ret = ex.AsStatus() 95 | } 96 | quit ret 97 | } 98 | 99 | /// Terminate all running subscriber jobs. 100 | ClassMethod TerminateAll() 101 | { 102 | set ret = $$$OK 103 | try { 104 | set job="" 105 | for { 106 | set job=$order($$$StatusJob(job)) 107 | quit:job="" 108 | $$$ThrowOnError(..Terminate(job)) 109 | } 110 | } catch ex { 111 | set ret = ex.AsStatus() 112 | } 113 | quit ret 114 | } 115 | 116 | /// Terminate a specific subscriber job. 117 | ClassMethod Terminate(pJob As %String) As %Status 118 | { 119 | set ret = $$$OK 120 | try { 121 | $$$LOG(pJob) 122 | do $system.Process.Terminate(pJob) 123 | $$$ThrowOnError(..CleanJob(pJob)) 124 | } catch ex { 125 | set ret = ex.AsStatus() 126 | } 127 | quit ret 128 | } 129 | 130 | /// Loop over $$$Queue and notify subscriber processes. 131 | /// This method should be used ONLY when system has been shut down and event queue must be reprocessed. 132 | ClassMethod RestartQueue() 133 | { 134 | set ret = $$$OK 135 | try { 136 | set channel="" 137 | for { 138 | set channel = $order($$$Queue(channel)) 139 | quit:channel="" 140 | 141 | set id = "", numMessages=0 142 | for { 143 | set id = $order($$$Queue(channel,id)) 144 | quit:id="" 145 | 146 | $$$LOG(channel_"->"_id) 147 | do $system.Event.Create($$$ResourceName(channel)) 148 | do $system.Event.Signal($$$ResourceName(channel), id) 149 | } 150 | } 151 | } catch ex { 152 | set ret = ex.AsStatus() 153 | } 154 | quit ret 155 | } 156 | 157 | /// Clean saved data for a job in Publisher-Subscriber data structures. 158 | ClassMethod CleanJob(pJob As %String) As %Status 159 | { 160 | set ret = $$$OK 161 | try { 162 | set channel = $get($$$StatusJob(pJob, "Channel")) 163 | if channel'="" { 164 | kill $$$StatusChannel(channel, "Job", pJob) 165 | } 166 | 167 | kill $$$StatusJob(pJob) 168 | } catch ex { 169 | set ret = ex.AsStatus() 170 | } 171 | quit ret 172 | } 173 | 174 | } 175 | 176 | --------------------------------------------------------------------------------