Dmitrii Baranov · Jul 16, 2025 go to post

I finally managed to solve the problem in Python. It's not perfect but it works:

Class User.Timer Extends %RegisteredObject
{

Property Executor [ Private ];

Method Initialize(maxWorkers As %Integer = 4) [ Language = python ]
{
import concurrent.futures
import time
import threading

self.Executor = concurrent.futures.ThreadPoolExecutor(max_workers=maxWorkers)
}

Method Close() [ Language = python ]
{
if self.Executor:
	self.Executor.shutdown()
}

Method Greet(name)
{
	Write "Hello ", name, !
}

Method OnCallback0(methodName As %String) [ Private ]
{
	Do $METHOD(instance, methodName)
}

Method OnCallback1(instance As %RegisteredObject, method As %String, arg1) [ Private ]
{
	Do $METHOD(instance, method, arg1)
}

Method OnCallback2(instance As %RegisteredObject, method As %String, arg1, arg2) [ Private ]
{
	Do $METHOD(instance, method, arg1, arg2)
}

Method OnCallback3(instance As %RegisteredObject, method As %String, arg1, arg2, arg3) [ Private ]
{
	Do $METHOD(instance, method, arg1, arg2, arg3)
}

Method OnCallback4(instance As %RegisteredObject, method As %String, arg1, arg2, arg3, arg4) [ Private ]
{
	Do $METHOD(instance, method, arg1, arg2, arg3, arg4)
}

Method OnCallback5(instance As %RegisteredObject, method As %String, arg1, arg2, arg3, arg4, arg5) [ Private ]
{
	Do $METHOD(instance, method, arg1, arg2, arg3, arg4, arg5)
}

Method InternalRun(delayMs As %Integer, wait As %Boolean, instance As %RegisteredObject, method As %String, args... As %List) [ Internal, Language = python ]
{
import time
import iris

if not self.Executor:
	raise Exception("The 'Initialize' method has not been called.")

def worker_function():
    time.sleep(delayMs / 1000)
    if len(args) == 0:
    	self.OnCallback0(instance, method)
    elif len(args) == 1:
        self.OnCallback1(instance, method, args[0])
    elif len(args) == 2:
        self.OnCallback2(instance, method, args[0], args[1])
    elif len(args) == 3:
        self.OnCallback3(instance, method, args[0], args[1], args[2])
    elif len(args) == 4:
        self.OnCallback4(instance, method, args[0], args[1], args[2], args[3])
    elif len(args) == 5:
        self.OnCallback5(instance, method, args[0], args[1], args[2], args[3], args[4])
    else:
        raise Exception("Too many arguments.")
    return 0
    
        
future = self.Executor.submit(worker_function)

# wait == 0 means fire-and-forget
try:
    if (wait == 1):
        rv = future.result()
    
except Exception as e:
    print(f"{e}")
}

/// delayMs  - the parameter specifies the timer delay in milliseconds
/// wait     - if the parameter is false, the process will not wait for the Future result to be returned (fire-and-forget)
/// instance - any object which method should be called with a delay
/// method   - specifies the object's callback method name
/// args     - the callback method arguments (up to 5)
Method Run(delayMs As %Integer, wait As %Boolean, instance As %RegisteredObject, method As %String, args... As %List)
{
	Do ..InternalRun(delayMs, wait, instance, method, args...)
}

ClassMethod Test()
{
	Set obj = ##class(Timer).%New()
	Do obj.Initialize()
	Do obj.Run(1000, 0, obj, "Greet", "John")
	Do obj.Run(2000, 0, obj, "Greet", "Jessica")
	Write "If 'wait == 0' this line will be printed first", !
	Do obj.Close()
}

}

Dmitrii Baranov · Feb 25, 2025 go to post

Here it is (implemented as Mixin):

Class MyNamespace.Pooled Extends Ens.Host [ Abstract ]
{

Property PoolIndex As %Integer [ Calculated ];

Method PoolIndexGet() As %Integer
{
    #Dim cn as %String
    Set cn = ..%ConfigName	
	
    #Dim statement as %SQL.Statement
    Set statement = ##class(%SQL.Statement).%New()
    Set status = statement.%PrepareClassQuery("Ens.Job","Enumerate")
    $$$ThrowOnError(status)
    
    #Dim rs as %SQL.StatementResult
    Set rs = statement.%Execute()
    #Dim i as %Integer = -1
    
    While (rs.%Next()) 
    {
		#Dim jobId as %String 
		Set jobId = rs.%Get("Job")
		If (rs.%Get("ConfigName") = cn)
		{
			Set i = i + 1
			If (jobId = $JOB) 
			{
				Kill rs
				Return i
			}
		}
    }
    Kill rs
    Return i
}

Property PoolSize As %Integer [Calculated];

Method PoolSizeGet() As %Integer
{
    #Dim cn as %String
    Set cn = ..%ConfigName
    #Dim statement as %SQL.Statement
    Set statement = ##class(%SQL.Statement).%New()
    Set status = statement.%PrepareClassQuery("Ens.Job","Enumerate")
    $$$ThrowOnError(status)
   
    #Dim rs as %SQL.StatementResult
    Set rs = statement.%Execute()
    #Dim i as %Integer = 0
    
    While (rs.%Next()) 
    {
		If (rs.%Get("ConfigName") = cn)
		{
			Set i = i + 1
		}
    }
    Kill rs
    Return i
}

}
Dmitrii Baranov · Dec 28, 2024 go to post

Found the answer. It is simply not allowed: "+ Rule: For collections of type document, message, searchset or collection, all entries must contain resources, and not have request or response elements"

Dmitrii Baranov · Dec 14, 2024 go to post

The error has gone after IRIS restart. Another indicator of the problem was that the SQL Gateway connection test was not working