Julius Kavay · Oct 31, 2025 go to post

JavaScript uses underscores as separators in (big)number literals.

<script>alert(1_234);</script> displays 1234
<script>alert(12_345_678_901_234_567_890_123n);</script>  displays 12345678901234567890123

Maybe there is a quirk/problem in VS terminal...

Julius Kavay · Sep 28, 2025 go to post

Oh yes, you are able to create an instance of

Class Your.Page Extends %CSP.Page
{
Property Name As %String [ InitialExpression = "Joe" ];
}

class because the inheritance goes as follows:
Your.Page <-- %CSP.Page <-- %Library.Base <-- %Library.SystemBase
and the %Library.SystemBase donates you the %New() method.

Julius Kavay · Sep 28, 2025 go to post

My guess is, because abstract classes (%CSP.Page is an abstract class) can't be instantiated, your subclass lacks the generator methos for property initialisation.

But there is a simple solution:

Class Your.Page Extends (%RegisteredObject, %CSP.Page)
{
Property Name As %String [ InitialExpression = "Joe" ];
}

Well, the world is right again

set page=##class(Your.Page).%New()
write page.Name --> Joe
Julius Kavay · Aug 18, 2025 go to post

Ha ha ha 😂, that's a big mistake. Those are old functions (for even older applications, maintained for backward compatibility only)  in the mean time all replaced by the $list...() functions.

Julius Kavay · Aug 18, 2025 go to post

Try it this way...

Class DC.OldStuff Extends %Persistent [ StorageStrategy = NewStorage1 ]
{

Property Rec As %Integer [ Identity ];
Property Name As %String;
Property City As %String;
Property Phone As %String;
Storage NewStorage1
{
<SQLMap name="Map1">
<Data name="City">
<RetrievalCode>s {*}=$zel(^myGlo("R",{L2}),2)</RetrievalCode>
</Data>
<Data name="Name">
<RetrievalCode>s {*}=$zel(^myGlo("R",{L2}),1)</RetrievalCode>
</Data>
<Data name="Phone">
<RetrievalCode>s {*}=$zel(^myGlo("R",{L2}),3)</RetrievalCode>
</Data>
<Global>^myGlo</Global>
<Subscript name="1">
<Expression>"R"</Expression>
</Subscript>
<Subscript name="2">
<Expression>{Rec}</Expression>
</Subscript>
<Type>data</Type>
</SQLMap>
<StreamLocation>^DC.OldS</StreamLocation>
<Type>%CacheSQLStorage</Type>
}

}

A short test shows, it works

USER>k ^myGlo
USER>s ^myGlo("R",1)=$zlp("John,Boston,11-22-33")
USER>s ^myGlo("R",5)=$zlp("Laura,New York,333-444-555")
USER>s ^myGlo("R",7)=$zlp("Paul,Chicago,556-666-777")
USER>d $system.SQL.Shell()
SQL Command Line Shell
----------------------------------------------------

The command prefix is currently set to: <<nothing>>.
Enter q to quit, ? for help.
USER>>select * from DC.OldStuff
3.      select * from DC.OldStuff

ID      City    Name    Phone   Rec
1       Boston  John    11-22-33        1
5       New York        Laura   333-444-555     5
7       Chicago Paul    556-666-777     7
3 Rows(s) Affected
statement prepare time(s)/globals/lines/disk: 0.0003s/5/159/0ms
          execute time(s)/globals/lines/disk: 0.0003s/13/1136/0ms
                          cached query class: %sqlcq.USER.cls43
---------------------------------------------------------------------------
USER>>quit

or as objects

USER>s obj=##class(DC.OldStuff).%OpenId(7)

USER>w obj.Name,!,obj.City,!,obj.Phone
Paul
Chicago
556-666-777
USER>
Julius Kavay · Aug 14, 2025 go to post

First step: create your own method, for example

Class DC.Unix [ Abstract ]
{
/// Convert Posix time into Timestamp
/// 
/// posix: posix time
///    df: date format
///    tf: time format
///    dp: decimal places
ClassMethod PosixToTimeStamp(posix, df = 3, tf = 1, dp = 0)
{
	set posix=posix-1152921504606846976/1E6
	quit $zdt(posix\86400+47117_","_(posix#86400),df,tf,dp)
}
}

Next step, if you need more speed, instead of the parameters <df> and <tf> use constants and remove <dp>. The very last step: shorten the method name from PosixToTimeStamp() to P2TS()

This way I got the (nearly) the same times as with (%Library.PosixTime).LogicalToTimeStamp, but without the need for string manipulation 

ICINDY:USER>k

ICINDY:USER>set posix = 1154669852181849976

ICINDY:USER>while $zh#1 {} s t=$zh f i=1:1:1E6 { s x=##class(%Library.PosixTime).LogicalToTimeStamp(posix) } w $zh-t
.902538
ICINDY:USER>while $zh#1 {} s t=$zh f i=1:1:1E6 { s x=##class(%Library.PosixTime).LogicalToTimeStamp(posix) } w $zh-t
.90609
ICINDY:USER>

ICINDY:USER>while $zh#1 {} s t=$zh f i=1:1:1E6 { s x=##class(DC.Unix).PosixToTimeStamp(posix) } w $zh-t
.934834
ICINDY:USER>while $zh#1 {} s t=$zh f i=1:1:1E6 { s x=##class(DC.Unix).PosixToTimeStamp(posix) } w $zh-t
.944418
ICINDY:USER>

ICINDY:USER>while $zh#1 {} s t=$zh f i=1:1:1E6 { s x=##class(DC.Unix).P2TS(posix) } w $zh-t
.913609
ICINDY:USER>while $zh#1 {} s t=$zh f i=1:1:1E6 { s x=##class(DC.Unix).P2TS(posix) } w $zh-t
.905303
ICINDY:USER>

ICINDY:USER>w $zv
IRIS for UNIX (Ubuntu Server LTS for x86-64) 2021.2 (Build 649U) Thu Jan 20 2022 08:49:51 EST
ICINDY:USER>w x
2025-05-27 12:06:15
ICINDY:USER>
Julius Kavay · Jul 20, 2025 go to post

You have a status (or error code), OK, now take that code and make it more readable:

set code=<put here your code>
write $system.Status.GetErrorText(code)

I think, it will be some kind of authentication issue...

Julius Kavay · Jul 7, 2025 go to post

In SystemManagementPortal goto:

SystemAdministration --> Configuration --> AdditionalSettings --> StartupSettings: JobServers

Julius Kavay · Jul 7, 2025 go to post

I think, Enrico was more aiming for the point that “concatenation is the gateway for SQL injection.”

Julius Kavay · Jul 3, 2025 go to post

An easy and simple (assuming standard storage strategy) way to get those informations (the slot numbers, where a property is stored) could be achieved with a simple classmethod

Class DC.PropInfo [ Abstract ]
{

/// Info about properties of a class:
/// - list of all properties stored in a list
/// - slot number for a given property
/// 
/// 1) add this class to your class definition
///    class some.class extends (%Persistent, DC.StorageInfo)  or
///    class some.class extends (%SerialClass, DC.StorageInfo)
///           
/// 2) then use it as follows
///    write ##class(some.class).PropInfo()      --> list of property names
///    write ##class(some.class).PropInfo("Age") --> slot number for the Age property
/// 
/// write ##class(
ClassMethod PropInfo(name = "") As %String [ CodeMode = objectgenerator ]
{
	set sto=%compiledclass.Storages, prp=0, list=""
	
	if sto.Count()=1 {
		set dat=sto.GetAt(1).Data
		for i=1:1:dat.Count() if dat.GetAt(i).Structure="listnode" set prp=dat.GetAt(i) quit
	
		if prp {
			if %compiledclass.ClassType="serial" { set list="", i=1 } else { set list=$lb(""), i=2 }
			for i=i:1:prp.Values.Count() set list=list_$lb(prp.Values.GetAt(i).Value)
		}
		
		do %code.WriteLine(" if name="""" quit """_$lts(list)_"""")
		do %code.WriteLine(" quit $lf($lfs("""_$lts(list)_"""),name)")
	}
	
	if list="" write !,"*** No properties found! ***"
	quit $$$OK
}

}

Two test classes

Class DC.TestPerson Extends (%Persistent, DC.PropInfo)
{
Property Name As %String;
Property Addr As DC.Address;
Property Salary As %Numeric;
Property Expertise As list Of %String;
}


Class DC.Address Extends (%SerialObject,DC.PropInfo)
{
Property Street As %String;
Property City As %String;
}

Now you can do things like:

write ##class(DC.TestPerson).PropInfo()         --> ,Name,Addr,Salary,Expertise
write ##class(DC.TestPerson).PropInfo("Salary") --> 4
// you can use the slot number for direct data access:
write $list(^DC.TestPersonD(id),slotnumber)  gives you the same value as
write ##class(DC.TestPerson).SalaryGetStored(id)

// the same game for serial classes
write ##class(DC.Address).PropInfo()       --> Street,City
write ##class(DC.Address).PropInfo("City") --> 2

// in case you have an instance
set pers=##class(DC.TestPerson).%OpenId(id)
write pers.PropInfo()      --> ,Name,Addr,Salary,Expertise
write pers.Addr.PropInfo() --> Street,City

// etc.

Julius Kavay · Jun 30, 2025 go to post

Another reason for the <CLASS or ROUTINE DOES NOT EXISTS> message is that you're in the wrong namespace. Your installation has four namespaces by default: %SYS, DOCBOOK, SAMPLES, and USER. Another possibility could be misleading YouTube videos that use classes from newer Cache or IRIS versions without notice.

Julius Kavay · Jun 8, 2025 go to post

First, measuring execution times on modern operating systems where multiple processes run in parallel (on multiple CPUs) is challenging. The following demo application assigns a value to a variable in four different ways:
– in a single method
– in two methods, both in the same class
– in two methods where one method code is in an inherited class, and
– in two methods where one method is in a different class

As expected, the first is the fastest (keyword: loop unrolling) and the last is the slowest, the other two take about the same time.

Class DC.Times Extends (%RegisteredObject, TimesAbstract)
{
ClassMethod ShowTimes()
{
	while $zh#1 {} set t1=$zh for i=1:1:1E6 { do ..Complete() } set t1=$zh-t1
	while $zh#1 {} set t2=$zh for i=1:1:1E6 { do ..OneClass() } set t2=$zh-t2
	while $zh#1 {} set t3=$zh for i=1:1:1E6 { do ..InhClass() } set t3=$zh-t3
	while $zh#1 {} set t4=$zh for i=1:1:1E6 { do ..TwoClass() } set t4=$zh-t4
	write $j(t1,9,5), $j(t2,9,5), $j(t3,9,5), $j(t4,9,5),!
}
/// The complete application is carried out in one method
ClassMethod Complete()
{
	set x=12345
	set y=12345
}
/// The entire application is done in the same class, but with different methods
/// Both methods are local (OneClass + LocTask)
ClassMethod OneClass()
{
	set x=..LocTask()
	set y=..LocTask()
}
/// The entire application is done in the same class, but with different methods
/// One method is local (InhClass) the other is inherited (InhTask)
ClassMethod InhClass()
{
	set x=..InhTask()
	set y=..InhTask()
}
/// The entire application uses two methods in two different classes
ClassMethod TwoClass()
{
	set x=##class(DC.Times2).ExtTask()
	set y=##class(DC.Times2).ExtTask()
}
/// As an "application" we simply return a constant value
ClassMethod LocTask(val)
{
	quit 12345
}
}

Class DC.Times2 Extends %RegisteredObject
{
/// As an "application" we simply return a constant value
ClassMethod ExtTask(val)
{
	quit 12345
}
}

Class DC.TimesAbstract [ Abstract ]
{
/// As an "application" we simply return a constant value
ClassMethod InhTask(val)
{
	quit 12345
}
}

Some time values

USER>

USER>f i=1:1:3 d ##class(DC.Times).ShowTimes()
  0.10833  0.19660  0.19649  0.22001
  0.10837  0.19657  0.19608  0.22000
  0.10826  0.19661  0.19603  0.21992

USER>

USER>f i=1:1:3 d ##class(DC.Times).ShowTimes()
  0.10998  0.19711  0.19643  0.22006
  0.10830  0.19657  0.19624  0.22013
  0.10822  0.19684  0.19628  0.22139

USER>

USER>w $zv
IRIS for UNIX (Ubuntu Server 22.04 LTS for x86-64) 2025.1 (Build 225_1U) Fri May 16 2025 12:18:04 EDT
USER>

Second, the choice of development method (use of include files, class inheritance, multiple classes, a large method in an even larger class, etc.) depends on other factors such as maintainability, runtime priority, etc.

Julius Kavay · Jun 5, 2025 go to post

If you want to work with class parameters, you can choose between two basic variants

Parameter GlobalName1 = "^Global";
Parameter GlobalName2 = {$name(^Global)};

ClassMethod Test()
{
	set @$name(@..#GlobalName1)@("index")="abc"	// using variante 1
	write @..#GlobalName2@("index")				// using variante 2
}
Julius Kavay · May 22, 2025 go to post

If you have set the "handling of undefined" settings to 2 then in either case (you set x to a nullstring OR x is undefined) in statements like

if $d(@x@(whatever))
set d=$d(@x@(whatever))

you get a <SYNTAX> error, because nullstring (the value of x) is not a valid name for a local or global variable.

In a terminal session issue the following commands and check each output with mine:

kill         // kill all local variables
write x=""   // the output is 1 (because of your setting)
if $d(@x)    // the output is a <SYNTAX> because "" is not a valid name
set d=$d(@x) // the output is a <SYNATX> because "" not a valid name

If your output is different from the above, than please contact the WRC
My $ZV is IRIS for UNIX (Ubuntu Server LTS for x86-64) 2021.2 (Build 649U) Thu Jan 20 2022 08:49:51 EST

Julius Kavay · May 22, 2025 go to post

I keep on nagging...
I expected to see the environment of your IF-line: is it in a block structure or not. Which variables are initialized, etc. But you always give me the same expressiveness information: just the IF-Statement.

Nevertheless, I think, I know, why you get the <SYNTAX> error:

- under "default conditions", executing the following lines gives you an <UNDEFINED> error

  kill
  set a=1,b=2   // x is not defined
  if $d(@x@(a,b))

- but the very same lines gives you a <SYNTAX> error if the setting, how undefined variables should be handled is set to 1 or 2 (I suppose, in your case it is set to 2) because x as nullstring is not a valid variable or global name.


To see the setting in question, go to:
ManagementPortal-->SystemAdministration-->AdditionalSettings-->Compatibility and look for the setting "Undefined". I'm pretty sure, there is a 2 (or maybe 1). The default setting is 0.

Hint: the above mentioned setting is a per job setting, you must start a new job to have the new setting in effect

Julius Kavay · May 22, 2025 go to post

I think you are leaving out some important information: you have a terminal session, but you are calling either a routine (.mac) or a (class) method, since a standard terminal session does not accept multi-lines (as you wrote above).
And when you call a routine or method, it would be nice to see that function/method. By the way, for indirection to work, you need variables with global visibility. So I'm assuming you're mixing some existing variables from your terminal session with variables from your routine/method.

Julius Kavay · May 21, 2025 go to post

It's not a permission issue!
A simple check tells you:

write ##class(%SYS.Namespace).GetRoutineDest(,"%Test") --> ^/opt/isc/icindy/mgr/irislib/

a routine named %Test will bes stored in the "irislib" database.
Now, if you take a look at the irislib database, you will see, it's a READONLY database, hence you get the <PROTECT> error. A readonly means read only, even for the Superuser!

If you absolutely need to name that routine with %-something (except %Z* and %z*) than:
- 1) remove the readonly flag from the database in question
  2) save your routine
  3) turn on the readonly flag
  4) for every change in that routine repeat the abowe game: 1), 2) and 3)
- and don't forget to make a copy of your %-something routine
  because each IRIS update REMOVES ALL %-routines except those %Z* and %z*
  

Julius Kavay · May 14, 2025 go to post

Despite the misleading error message, the solution (with the help of WRC) is:  IRIS  native API requires a %Developer role

Julius Kavay · May 12, 2025 go to post

Just to put things into a correct light

1) java came out mid 90es
at the time ObjectScript (or mumps or m) already had it's lock a quarter centuy. That said, one could ask, why didn't implemented Java the lock the same way as M? Maybe they didn't know about mumps, maybe they targeted something else, who knows...  On the other hand, imagine, all the programming lanuages have the same commands, functions, syntaxes etc. Then all the time we just had one programming lanuage.
  
2) as you said, lock is a 'convention'. A convention, as many other things.
It's a convention too, if you take a tramway you buy a ticket - and most people do buy a ticket, but some are fare dodger. And nobody says, that's the problem of the convention.
  
3) using a device as a program interlock did not targeted the lock command but setting a global.
If you set a global node to mark something as 'occupied' and during this time your process dies, then the global won't be cleaned up by the system - except you set a process-private-global, but that mark is invisible to external processes.

Julius Kavay · May 12, 2025 go to post

I'm neither a C# nor a Java guru, but I know definitively that a LOCK in ObjectScript protects YOUR resources to be  to be taken by OTHERS. It does NOT prevents you, to use your OWN resource again and again. And yes, I know that you can get the lock count from ^$LOCK SSV. Locks like

lock +^Test:1 if $test lock +^Test:1 if $test write "It's my resource!"
lock -^Test,-^Test

are pointless. The above locks could be a short version for:
 

(Class)Method MyCrticalSection()
{
  lock +^Test:1
  if '$test quit
  
  // some commands
  do ..MyCriticalSection() // despite the lock, this call enters this section again
  // some more commands
  
  lock -^Test
}

whereas the above lock guards against OTHER jobs to enter that method, it doesn't locks my own job out.
Maybe I don't understand your problem. People use to say, if there is a problem I do not understand, than either the problem is very simple or extraordinarily complicated...

Julius Kavay · May 7, 2025 go to post

I don't entirely understand the problem, you ask for "inside same process" but further you write "protect some code for being called by multiple processes at same time" - same or multiple processes?

The only way (I know) to reenter a code inside the same process is, using recursion (there are neither threads nor events in ObjectScript). If you don't want reentrace, do not use recursion.

If you talk about multiple processes (running at the same time) then you have to use some kind of semaphore to let in just one process at any given time in a critical code section, see the examples below. I would use the version with device, because in all error cases (abnormal shut down, process error, etc) IRIS closes the device automagically - globals may have leftovers in case of errors.

Class DC.OnlyOne Extends %RegisteredObject
{

/// device numbers 20-46 and 200-223 are routine interlock devices
/// Choose one of them
/// 
ClassMethod UsingDevice(testTime = 0)
{
	set myDevice = 45
	open myDevice::0
	
	if $t {
	
		// run that critical section
		hang testTime
		set ans="Critical section is done"
	
	} else { set ans = "Critical section in use" }
	
	close myDevice
	quit ans
}

/// a more meaningful name then ^X would be reasonable
/// a KILL ^X in %ZSTART.mac, label SYSTEM is also advisable
/// for the case, the system was shut down abnormally
ClassMethod UsingGlobal(testTime = 0)
{
	if $i(^X)=1 {
		
		// run that critical section
		hang testTime
		set ans="Critical section is done"
		
	} else { set ans="Critical section in use" }
	
	if $i(^X,-1)
	quit ans
}

ClassMethod UsingLock(tim = 10)
{
	// see John's example
}

}
Julius Kavay · May 5, 2025 go to post

I had a real use case, where from an instance method a class method was called and that class method used $this as argument in a subsequent call. In the very first call the content of $this was the class name (as documented) but in subsequent calls it contained the OREF and that caused a <CLASS DOES NOT EXIST> error. A WRC ticket is already open.

Julius Kavay · Apr 14, 2025 go to post

Since you want the smallest possible number of sets that cover the maximum number of elements, one of the simplest solutions (from a programming point of view) is to check all possible combinations of K out of N sets.

However, keep in mind that combinations grow (very) quickly. If your N tends to leave the single-digit range, you may need to resort to linear programming methods.

Some values for all combinations (K out of N) to see, how they grow:
 N : 1  2  3   4   5     10      15         20          25                     50
cnt: 1  3  7  15  31  1,023  32,767  1,048,575  33,554,431  1,125,899,906,842,623

Class DC.MaxCoverage Extends %RegisteredObject
{

ClassMethod Test(case = 0)
{
	if case=4 {
		set list($i(list))="1,2,3"
		set list($i(list))=""
		set list($i(list))="1,2,3"
	}
	elseif case=3 {
		set list($i(list))="1,2,3"
		set list($i(list))="4,5,6"
		set list($i(list))="1,2,3"
		
	} elseif case=2 {
		set list($i(list))="1,2,3"
		set list($i(list))="7,8,9"
		set list($i(list))="11,12,13,14,15"
		set list($i(list))="2,8,12"
		set list($i(list))="15,16,17"
		set list($i(list))="12,17,19,21,22,23"
		set list($i(list))="11,21,31,32,33"
		set list($i(list))="34,35,36"
		
	} elseif case=1 {
		set list($i(list))="1,2,3,4"
		set list($i(list))="5,6,7,8,9,10,11,12"
		set list($i(list))="2,3,4,5,6,7"
		
	} else { // the original testcase
		set list($i(list))="3,5,6,7,9"
		set list($i(list))="1,2,6,9"
		set list($i(list))="5,8,9"
		set list($i(list))="2,4,6,8"
		set list($i(list))="4,7,9"
	}
	
	quit ..MinMax(.list)
}

/// minimum number of sets with maximum number of coverage
/// 
ClassMethod MinMax(ByRef list)
{
	// I assume, all numbers are
	// a) integers and greater then 0 (else map the numbers to integers in the toBits() method)
	// b) and all sets have less then ca. 262100 elements
	// 
	set N=list
	for i=1:1:N s set(i)=..toBits(list(i))	// convert each list into a bitstring
	
	set max=0, min=N, lst=""				// max=covered numbers, min=required sets, lst=list of combinations
	
	for k=1:1:N {									// compute all (k out of n) combinations for each k
		set s=0, i(0)=0								// --+ compute k out of n
1		set s=s+1, i(s)=i(s-1), e(s)=N-k+s			//   |
2		for i(s)=i(s)+1:1:e(s) goto 1:s<k do chk	//   | check a particular combination
3		set s=s-1 if s goto 2:i(s)<e(s),3			// --+
	}
	quit min_" set(s) covers "_max_" elements with sets: "_lst	// return the result	
	
chk	set c=i(1),v=set(i(1))									// take the first set
	for i=2:1:s s c=c_","_i(i), v=$bitlogic(v|set(i(i)))	// OR it with the next
	set v=$bitcount(v,1)
	
	if v>max { set max=v,min=s,lst=c }
	elseif v=max,s<min { set min=s,lst=c }
	elseif v=max,s=min { s lst=lst_" or "_c }
}

/// convert a list of numbers into bitstring, i.e.: 1,2,4 --> 1101
/// 
ClassMethod toBits(x)
{
	set b=""
	for i=1:1:$l(x,",")-(x="") set $bit(b,$p(x,",",i))=1
	quit b
}

/// Sum of all combinations of N elements
/// 
ClassMethod AllComb(N)
{
	s s=0
	f k=1:1:N s s=s+..Comb(k,N)
	q s
}

/// Count of combinations of K elements out of N elements
/// 
ClassMethod Comb(k, n)
{
	s c=1
	f k=1:1:k s c=c/k*n, n=n-1
	q c
}

}

Tests


USER>for i=0:1:4 write ##class(DC.MaxCoverage).Test(i),!
3 set(s) covers 9 elements with sets: 1,2,4
2 set(s) covers 12 elements with sets: 1,2
7 set(s) covers 23 elements with sets: 1,2,3,5,6,7,8
2 set(s) covers 6 elements with sets: 1,2 or 2,3
1 set(s) covers 3 elements with sets: 1 or 3

USER>
Julius Kavay · Apr 12, 2025 go to post

Two questions.

First, is the expected answer for the above collection of five sets: 1,2,4 (where the sets 1, 2 and 4 all together cover 9 different numbers: 3,5,6,7,9,1,2,4,8)?

Second, may any of the sets contain a number, which is not included in the AllList?
If the answer is a no (what I assume), then the AllList is't needed, because all numbers are in 'AllList', hence it's enough to search for a minimum number of sets with the maximum of distinctive elements.

Julius Kavay · Mar 13, 2025 go to post

There is a somewhat "simple" method too

/// search for the superserver job
/// return the OS Username for that job
ClassMethod OSUsername()
{
	new $namespace
	set $namespace="%SYS"
	
	set job=""
	for {set job=$zj(job) quit:job=""
		set prc=##class(%SYS.ProcessQuery).%OpenId(job)
		if prc, prc.JobType=24 ret prc.OSUserName
	}
	ret ""
}
Julius Kavay · Mar 13, 2025 go to post

Try this method

Class DC.Util Extends %RegisteredObject
{

/// Return: the OS username for this Cache/IRIS instance
/// 	
/// First, get the port of the superserver
/// then search, which job owns that port
/// then return the OSUsername for that job
/// 
ClassMethod OSUsername()
{
	new $namespace
	set $namespace="%SYS"
	
	if ##Class(Config.Startup).Get(.par),$d(par("DefaultPort"),port) {
		set job="", pattern=".e1""|TCP|"_port_"*"".e"
		for  set job=$zj(job) quit:$v(-1,job)?@pattern||(job="")
		
		if job {
			set proc=##class(%SYS.ProcessQuery).%OpenId(job)
			ret:proc proc.OSUserName
		}
	}
	ret ""
}

}

A note:
- I know of a $zu(...) function which works and returns the superserver port but $zu() functions are  deprecated/discouraged  
- and this one is not in the replacement list - why?