Dmitry Maslennikov · Jun 23, 2016 go to post

As I'm not sure what you mean by 'dead' locks, I'll just show simple code for looking all locks in current namespace

set lockName=""for {set lockName=$order(^$LOCK(lockName))quit:lockName=""set mode=^$LOCK(lockName,"MODE")set owner=^$LOCK(lockName,"OWNER")write !,mode,?4,owner,?12,lockName}
Dmitry Maslennikov · Jun 22, 2016 go to post

Studio reformats some class parts, such as Methods defenitions when you save it.

But as I know feature Atelier, stores class definition as is, so you do not need more such option, just format class as you need, and it should keep so.

Dmitry Maslennikov · Jun 17, 2016 go to post

As I've already had some experience in such systems in production, and one of our projects, has similar architecture exclude docker, just on windows, some physical servers with ECP-Client + CSPGateway + Apache, and one HAPorxy server for all of this server. And in this case all this scheme is quite static, and adding new ECP-client means some manual works, on all levels. But with docker I expect, just call something like this command 

docker-compose scale ecp=10

and just get some new working instances, which just after gets their new web-clients

To work it as microservices and split CSPgateway and Cache instance as different containers, I need to have simple package just only with CSPGateway, but FieldTest versions does not contain it. But yes sure, I think it is good way too, and in this case I can have more WEB-containers then Cache does, if it would be needed.

Dmitry Maslennikov · Jun 17, 2016 go to post

Timur, you can look at my example at github which I wanted to use in to article about using docker but have not managed yet. In this example I have Dockerfile for ECP-client, which can be build for particular ECP-server. And with %Installer manifest possible to make backward data channel too, while we have already known about where our server placed, we can connect to him via %Net.RemoteConnect or something else and make new backward connection, the problem is in this case how to remove old ones. I played only on one machine. But in anyway, Ansible still could be useful, but in case to prepare servers to work in docker cluster,  which should be prepared before we could use docker-compose and so on. And My example also contains web-server (apache), to have an access to this new instance. What I wanted to do then is to use some load balancer, HAProxy or traefik (as recommended Luca), to get one point access to my application, and dynamically expandable, without any manual operations, except scaling.

Dmitry Maslennikov · Jun 14, 2016 go to post

Yes, such language extension is very useful, but there is one more feature less known, and unfortunately completely undocumented. Its structured system variables, some of them could me familiar it is: ^$JOB,  ^$LOCK, ^$GLOBAL and ^$ROUTINE. More information in documentation here. But not all knows that it is possible to make your own such global. You should create routine with name SSVN"global_name" in %SYS namespace, for example SSVNZA for ^$ZA global

and for example with this code below, you SET, GET, KILL any values in this global, and this global will contain individual data for every session, and it is possible to use $order or even $query for  such global

ROUTINE SSVNZA
#define global $na(^CacheTemp.ZA($select($isobject($get(%session)):%session.SessionId,1:$job)))

fullName(glob) {
    set global=$$$global
    for i=4:1:glob set global=$na(@global@(glob(i)))
    quit global
}
set(glob, value) public {
    set global=$$fullName(.glob)
    set @global=value
}
get(glob) public {
    set global=$$fullName(.glob)
    quit @global
}
kill(glob, zkill=0) public {
    set global=$$fullName(.glob)
    if zkill {
        zkill @global
    } else {
        kill @global
    }
}
order(glob, dir) public {
    set global=$$fullName(.glob)
    quit $order(@global, dir) 
}
query(glob, dir) public {
    set global=$$fullName(.glob)
    set result=$query(@global, dir)
    quit:result="" ""
    set newGlobal=$name(^$ZA)
    for i=$ql($$$global)+1:1:$ql(result) {
        set newGlobal=$name(@newGlobal@($qs(result,i)))
    }
    quit newGlobal 
}

But as I said before, as it is completely undocumented feature a bit difficult to get complete compatibility with ordinary globals. But it still could be useful, in other cases, like it used by InterSystems.

but WaitMsg it is on parent's side, my control code ususaly like like this

    set resName="someresource"
    job ..someJob(jobsData,resName)
    set child=$zchild
    
    for {
        set $lb(sc,data)=$system.Event.WaitMsg(resName, 1)
        if sc<=0 {
            quit:'$data(^$Job(child))
        }
        continue:data=""
        // do some staff with incomming data
    }

But if you want to interrupt your child process, of course will be possible only from such process, if your process have some loops you may just check if parent still exists, if not, just stop working.

Dmitry Maslennikov · May 30, 2016 go to post
ToPhone(s = "")
{
 set r=""
 set c=96 
 for p=2:1:9 {
     set k="" 
     for j=1:1:3+(p=7)+(p=9) { 
         set k=k_p
         set c($c($i(c)))=k
     }
 } 
 for i=1:1:$l(s) { 
     set n=$g(c($e(s,i)),0) 
     if $e(n)=$e(r,*) set r=r_" " 
     set r=r_n
 } 
 quit r
}
Dmitry Maslennikov · May 30, 2016 go to post

and 149, with a little bit changes

ToPhone(s = "")
{
 s r="",c=96 f p=2:1:9{s k="" f j=1:1:3+(p=7)+(p=9) s k=k_p,c($c($i(c)))=k} f i=1:1:$l(s){s n=$g(c($e(s,i)),0) s:$e(n)=$e(r,*) r=r_" " s r=r_n} q r
}
Dmitry Maslennikov · May 30, 2016 go to post

Well, my code below

toPhone(s  = "") {
  s r="",c=96,p=1 f i=3,3,3,3,3,4,3,4{s:$i(p) k="" f j=1:1:i s k=k_p,c($c($i(c)))=k} f i=1:1:$l(s){s n=$g(c($e(s,i)),0) s:$e(n)=$e(r,*) r=r_" " s r=r_n} q r
}

In first I'm generate a plane array with all variants, and then just get all numbers from it, include space between identical numbers, and zero for unknown symbols such as space

Dmitry Maslennikov · May 26, 2016 go to post

Actually, XData content it is a just XML, in it is stored exactly as you wrote it in IDE. But it is possible to compile any XData to any other format as you need, at this portal you can find two articles about how to do it, first and second. In your case it could be much easier, like some method which in compile mode gets all XData content and place it to &html<>, which then compile again as usual, and what you need after it is just call this generated method, to show this html. 

Dmitry Maslennikov · May 26, 2016 go to post

Steve,

I would recommend to distant webservers from ECP Application servers. In your case I would install webserver exactly on the server where ECP Application Server have already installed, so in this case your connect via superport will be secured, and in this case only web port should be opened in the firewall. Outside of this servers after firewall you need to use some load balancer, in this case I would recommend to use HAProxy. Connection between extarnal HAProxy and  internal webservers could be secured with ssl, but I'm not sure that is really needed.

And in this case it is possible to store all static content such as JS, CSS and images on server where load balancer placed, to avoid redundant request to the data servers. It is not a big problem for the production systems.

So finally in simple view it my looks like below.

Dmitry Maslennikov · May 18, 2016 go to post

It's is not good idea, not all time and not everywhere I have an access to google, but I have an access to my installation, and I have to search on my local machine. And such search should be support browser's search engines. And then I could do something like this.

with settings

But Ensemble documentation, should provide their own search engine to install in my browser without any manual operations. As it possible to do with AddSearchProvider

Dmitry Maslennikov · May 13, 2016 go to post

Many times I need to get some link from documentation, to show particular part to someone. And some time it is quite difficult to get such link, and the worst place in Class References, where I can't get good link for a particular part of class.

So, what I need is, an active icon with link, which then I can use, and for Class reference too.

As an example github.

Dmitry Maslennikov · May 10, 2016 go to post

As for me, when I have to investigate some strange behavior in my application, I prefer to read all journal files or limited by time, and then all records, and then I have some some to find exactly what I need, like only kill records or only sets, and only for some concrete global references. So, my usual code looks like this:

     set exit=0
    set docId="14105401"
    set jrnFile=##class(%SYS.Journal.System).GetCurrentFileName()
    do {
        !!,jrnFile,!
        set jrn=##class(%SYS.Journal.File).%OpenId(jrnFile)
        quit:'$isobject(jrn)
        
        #dim rec As %SYS.Journal.Record = jrn.FirstRecord
        set last=jrn.End
        set pr=0,opr=0
        do {
            set pr=$j(rec.Address/last*100,0,2)
            if pr'=opr write $c(13),pr_"%" set opr=pr
            
            if rec.%IsA("%SYS.Journal.SetKillRecord") {
                set glb=rec.GlobalNode
                if $qs(glb, 0),"^My.Global",$qs(glb, 1)=docId {
                    !!,rec.Address,!,glb
                    !
                }
            }
            
            set rec=rec.Next
            read q:0 set exit=q="q"
            quit:exit
        while $isobject(rec)
        quit:exit
        
        set sc=jrn.GetPrev(jrnFile, .jrnFile)
    while $$$ISOK(sc)
    quit

This code looks all journal files, file by file from latest to oldest, and show progress for current journal file, and shows offset if it found something interesting

In my opinion, it's more looks like by mistake, not sure why it could be done in other ways, it still working, with some limitations but any way.
We can wait what would say InterSystems, may be they will fix it in future.

You could just pass this arguments in Execute method, something like this

    new $namespace
    set $namespace="%sys"Set tRS = ##class(%ResultSet).%New("%SYS.Journal.File:Search")Set tSC = tRS.Execute("test", "/opt/cache/mgr/journal/20160507.001")if $$$ISERR(tSC) {do $system.OBJ.DisplayError(tSC)}do tRS.%Display()

this code will return all offset's which contains text - 'test'

You must remember that this query should be executed only in %SYS namespace.

Usually we could use some another modern ways, which allow us to call queries.  

    set statement=##class(%SQL.Statement).%New()set tSC=statement.%PrepareClassQuery("%SYS.Journal.File","Search")if $$$ISERR(tSC) {do $system.OBJ.DisplayError(tSC)}Set tRS = statement.%Execute("test", "/opt/cache/mgr/journal/20160507.001")

or even much shorter

    set tRS=##class(%SYS.Journal.File).SearchFunc("test", "/opt/cache/mgr/journal/20160507.001")do tRS.%Display()

But unfortunately such code snippets does not work in case when Query does not contain any parameters in his original declaration.

126 error means - The specified module could not be found.

So, you have dependency issue, so I recommend to use dependency tool which I suggested before, to find what you need.

After this error you should look at cconsole.log, where you can find error code, why loading was failed.
Mostly there are two errors with loading dll in cache,  using 32bit dll instead of 64bit. And if this library has unsatisfied dependencies, in this case I'm using this tool to find such dependencies.

Well, you can't write conditions in this way, and compile error which you see, it is actually parsing error for such condition
How you can change it, You should define some function which may do all this check or partially, just return some value for check.

To define such function, you should have your child for Ens.Rule.FunctionSet class, something like this

Class Test.Utils Extends Ens.Rule.FunctionSet
{

ClassMethod GetAt(value As %String = "", index As %String) As %String
{
    if $isobject(value) {
        quit value.GetAt(index)
    }
    quit ""
}

}

and then, you can use it in this way, where GetAt your just created function.

<rule name=""><constraint name="msgClass" value="Test.TestMessage"></constraint><when condition="GetAt(Document.myList,1)=&quot;AA&quot;"><send transform="" target="DummyOperation"></send><return></return></when></rule>

But your function should return final value for checking, and you can't use write so,

GetAt(Document.myList,1).property1=&quot;AA&quot;

Only two way to do it, Mapping with %ALL namespace as suggested Timothy, or %-class which as you already know by default mapped to all namespace, but I said how you should name your class, to send this class to Write-enabled database.

First of all you should know that namespace it is not final destination for any data in Caché. Namespace just defines easy access to set of data, but all data stores in databases. And any security settings uses on Databases not for Namespaces. Data

There some reasons why you can't just use any %-classes; 

  • By default all %-classes, appeared in CACHELIB database, which read-only by default after installation.
  • CACHELIB database after any installation process, will be overwritten with all you custom content.

Actually first point is not so truly, because InterSystems gives us one chance to create %-class, without loosing it after next upgrade. You should use %Z* or %z* name for classes and routines (some names have already reserved for InterSystems use), and in this case that classes will be stored at CACHESYS database, which available to write, and any upgrade will not rewrite such classes. 

So, you should read this page in documentation, to know more about names, which names you can't use, and how to name recommend.

Dmitry Maslennikov · Apr 28, 2016 go to post

Sure, it should work, CACHE.DAT format is platform independent. You even can mount to another version, lower or higher.