├── Source ├── ynys │ ├── processors.dyalog │ ├── stop.dyalog │ ├── tracelog.dyalog │ ├── Failed.dyalog │ ├── dateNo.dyalog │ ├── Available.dyalog │ ├── Running.dyalog │ ├── _IO.dyalog │ ├── easy.dyalog │ ├── minuscule.dyalog │ ├── Values.dyalog │ ├── and.dyalog │ ├── messages.dyalog │ ├── InternalState.dyalog │ ├── ynys.dyalog │ ├── checkPortIsFree.dyalog │ ├── qsignal.dyalog │ ├── callerSpace.dyalog │ ├── getDefaultWS.dyalog │ ├── validateRemoteFilters.dyalog │ ├── Description.dyalog │ ├── InitConnections.dyalog │ ├── addWSpath.dyalog │ ├── checkWs.dyalog │ ├── dix.dyalog │ ├── publicMethods.dyalog │ ├── ll.dyalog │ ├── sessionStart.dyalog │ ├── checkLocalServer.dyalog │ ├── SendWait.dyalog │ ├── New.dyalog │ ├── whoami.dyalog │ ├── llEach.dyalog │ ├── llOuter.dyalog │ ├── newSession.dyalog │ ├── cleanDM.dyalog │ ├── connect.dyalog │ ├── Config.dyalog │ ├── State.dyalog │ ├── until.dyalog │ ├── argType.dyalog │ ├── getDRC.dyalog │ ├── derv.dyalog │ ├── DRCClt.dyalog │ ├── prof.dyalog │ ├── InitConnection.dyalog │ ├── llKey.dyalog │ ├── Stats.dyalog │ ├── llRank.dyalog │ ├── cleanup.dyalog │ ├── receive.dyalog │ ├── StartServer.dyalog │ ├── suicide.dyalog │ ├── fnSpace.dyalog │ ├── encode.dyalog │ ├── isoStart.dyalog │ ├── AddServer.dyalog │ ├── expose.dyalog │ ├── RemoveServer.dyalog │ ├── isolates.dyalog │ ├── Reset.dyalog │ ├── localServer.dyalog │ ├── decode.dyalog │ ├── getSet.dyalog │ ├── InitProcesses.dyalog │ ├── execute.dyalog │ ├── Init.dyalog │ ├── setDefaults.dyalog │ └── proxySpace.dyalog ├── isolate │ ├── Stats.dyalog │ ├── Reset.dyalog │ ├── AddServer.dyalog │ ├── State.dyalog │ ├── isSlave.dyalog │ ├── New.dyalog │ ├── Config.dyalog │ ├── Failed.dyalog │ ├── RemoveServer.dyalog │ ├── Available.dyalog │ ├── Running.dyalog │ ├── StartServer.dyalog │ ├── Values.dyalog │ ├── RPCServer.dyalog │ └── APLProcess.dyalog └── root │ ├── IIRank.dyalog │ ├── IIOuter.dyalog │ ├── IIEach.dyalog │ ├── IIKey.dyalog │ ├── New.dyalog │ ├── II.dyalog │ └── ll.dyalog ├── .gitignore ├── Tests ├── teardown.dyalog ├── FindWs.dyalog ├── all.dyalogtest ├── expect.dyalog ├── test_callbacks.dyalog ├── test_homeport.dyalog ├── test_runtime.dyalog ├── setup.dyalog ├── test_errors.dyalog ├── test_syntax.dyalog └── test_basic.dyalog ├── isolate.dyalogbuild ├── LICENSE ├── README.md ├── Samples ├── IIPageStats.dyalog ├── AWSIsolates.dyalog └── AWS.dyalog ├── OriginalNotes ├── configuration.txt └── isolate.txt └── Build └── Build.dyalog /Source/ynys/processors.dyalog: -------------------------------------------------------------------------------- 1 | processors←{1111⌶⍬} 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | isolate.dws 2 | Tests/*.rng.txt 3 | *.rng.txt 4 | -------------------------------------------------------------------------------- /Source/ynys/stop.dyalog: -------------------------------------------------------------------------------- 1 | stop;⎕TRAP 2 | ⎕TRAP←0 'S' ⋄ ∘ 3 | -------------------------------------------------------------------------------- /Source/ynys/tracelog.dyalog: -------------------------------------------------------------------------------- 1 | tracelog←{⎕←((1⊃⎕SI),'[',(⍕1⊃⎕LC),']')⍵} 2 | -------------------------------------------------------------------------------- /Source/ynys/Failed.dyalog: -------------------------------------------------------------------------------- 1 | r←Failed name 2 | r←(1⊃(1⊃⎕RSI).(702⌶)name)∊2 3 3 | -------------------------------------------------------------------------------- /Source/ynys/dateNo.dyalog: -------------------------------------------------------------------------------- 1 | dateNo←{⍺←⊢ 2 | ⊢2 ⎕NQ'.' 'DateToIDN'⍵ 3 | } 4 | -------------------------------------------------------------------------------- /Source/ynys/Available.dyalog: -------------------------------------------------------------------------------- 1 | r←Available name 2 | r←(1⊃(1⊃⎕RSI).(702⌶)name)∊4 5 3 | -------------------------------------------------------------------------------- /Source/ynys/Running.dyalog: -------------------------------------------------------------------------------- 1 | r←Running name;complete;failed 2 | r←0=1⊃(1⊃⎕RSI).(702⌶)name 3 | -------------------------------------------------------------------------------- /Source/ynys/_IO.dyalog: -------------------------------------------------------------------------------- 1 | :Namespace _IO 2 | 3 | ##.⎕IO←0 4 | 5 | :EndNamespace 6 | -------------------------------------------------------------------------------- /Source/isolate/Stats.dyalog: -------------------------------------------------------------------------------- 1 | Stats←{ 2 | 0::(⊃⍬⍴⎕DM)⎕SIGNAL ⎕EN 3 | ynys.Stats ⍵ 4 | } 5 | -------------------------------------------------------------------------------- /Tests/teardown.dyalog: -------------------------------------------------------------------------------- 1 | r←teardown dummy 2 | ⍝ Tear down isolate tests 3 | {}#.isolate.Reset 0 4 | r←'' 5 | -------------------------------------------------------------------------------- /Source/root/IIRank.dyalog: -------------------------------------------------------------------------------- 1 | IIö←{⍺←⊢ 2 | 0::(⊃⍬⍴⎕DM)⎕SIGNAL ⎕EN 3 | ⍺(⍺⍺ #.isolate.ynys.llRank ⍵⍵)⍵ 4 | } 5 | -------------------------------------------------------------------------------- /Source/ynys/easy.dyalog: -------------------------------------------------------------------------------- 1 | easy←{⍺←⊢ 2 | expose ⎕THIS(↓##.⎕NL 3 4) 3 | ⍝ expose public functions in root 4 | } 5 | -------------------------------------------------------------------------------- /Source/ynys/minuscule.dyalog: -------------------------------------------------------------------------------- 1 | minuscule←{⍺←⊢ 2 | ('abcdefghijklmnopqrstuvwxyz',,⍵)[(⎕A,,⍵)⍳⍵] 3 | ⍝ 4 | } 5 | -------------------------------------------------------------------------------- /Source/root/IIOuter.dyalog: -------------------------------------------------------------------------------- 1 | o_II←{⍺←⊢ 2 | 0::(⊃⍬⍴⎕DM)⎕SIGNAL ⎕EN 3 | ⍺(⍺⍺ #.isolate.ynys.llOuter ⍵⍵)⍵ 4 | } 5 | -------------------------------------------------------------------------------- /Source/isolate/Reset.dyalog: -------------------------------------------------------------------------------- 1 | Reset←{ ⍝ Reset all isolate processes 2 | 0::(⊃⍬⍴⎕DM)⎕SIGNAL ⎕EN 3 | ynys.Reset ⍵ 4 | } 5 | -------------------------------------------------------------------------------- /Source/ynys/Values.dyalog: -------------------------------------------------------------------------------- 1 | r←{default}Values name 2 | :If 0=⎕NC'default' ⋄ default←⊢ ⋄ :EndIf 3 | r←⊃default(1⊃⎕RSI).(702⌶)name 4 | -------------------------------------------------------------------------------- /Source/ynys/and.dyalog: -------------------------------------------------------------------------------- 1 | and←{⍺←⊢ 2 | ⍺⍺⊣⍵:⍵⍵⊣⍵ 3 | 0 4 | ⍝ left to right checking 5 | ⍝ try ⍵⍵ only if ⍺⍺ true 6 | } 7 | -------------------------------------------------------------------------------- /Source/ynys/messages.dyalog: -------------------------------------------------------------------------------- 1 | messages←{⍺←⊢ 2 | {(+/∨\' '≠⌽⍵)↑¨↓⍵}⍵{(0,⍴,⍺)↓⍵⌿⍨>/⍺⍷⍵}⎕CR⊃1↓⎕XSI 3 | ⍝ attached to caller 4 | } 5 | -------------------------------------------------------------------------------- /Source/isolate/AddServer.dyalog: -------------------------------------------------------------------------------- 1 | AddServer←{ ⍝ ⍵: ip-address port-numbers 2 | 0::(⊃⍬⍴⎕DM)⎕SIGNAL ⎕EN 3 | ynys.AddServer ⍵ 4 | } 5 | -------------------------------------------------------------------------------- /Source/isolate/State.dyalog: -------------------------------------------------------------------------------- 1 | State←{ ⍝ Report on state of isolate processes 2 | 0::(⊃⍬⍴⎕DM)⎕SIGNAL ⎕EN 3 | ynys.State ⍵ 4 | } 5 | -------------------------------------------------------------------------------- /Source/isolate/isSlave.dyalog: -------------------------------------------------------------------------------- 1 | r←isSlave ⍝ Return 1 if the current process is an isolate server 2 | r←'isolate'≡RPCServer.GetEnv'isolate' 3 | -------------------------------------------------------------------------------- /Source/isolate/New.dyalog: -------------------------------------------------------------------------------- 1 | New←{⍝ Return new isolate with contents specified by ⍵ 2 | 0::(⊃⍬⍴⎕DM)⎕SIGNAL ⎕EN 3 | ynys.New⊢⍵ 4 | } 5 | -------------------------------------------------------------------------------- /Source/isolate/Config.dyalog: -------------------------------------------------------------------------------- 1 | Config←{ ⍝ ⍵: option new-value (or '' to list current settings) 2 | 0::(⊃⍬⍴⎕DM)⎕SIGNAL ⎕EN 3 | ynys.Config ⍵ 4 | } 5 | -------------------------------------------------------------------------------- /Source/isolate/Failed.dyalog: -------------------------------------------------------------------------------- 1 | Failed←{⍝ 1 if an items of ⍵ was a future but computation failed, else 0 2 | 0::(⊃⍬⍴⎕DM)⎕SIGNAL ⎕EN 3 | ynys.Failed ⍵ 4 | } 5 | -------------------------------------------------------------------------------- /Source/isolate/RemoveServer.dyalog: -------------------------------------------------------------------------------- 1 | RemoveServer←{⍝ Remove remote server added with AddServer 2 | 0::(⊃⍬⍴⎕DM)⎕SIGNAL ⎕EN 3 | ynys.RemoveServer ⍵ 4 | } 5 | -------------------------------------------------------------------------------- /Source/ynys/InternalState.dyalog: -------------------------------------------------------------------------------- 1 | InternalState←{⍺←⊢ 2 | newSession'':⍳0 0 3 | {⍵.({⍵,⍪⍎⍕⍵}↓⎕NL 2)}⍣('namespace'≢minuscule ⍵)⊢session.assoc 4 | ⍝ 5 | } 6 | -------------------------------------------------------------------------------- /Source/root/IIEach.dyalog: -------------------------------------------------------------------------------- 1 | IÏ←{ ⍝ Model of Parallel Each. Usage [⍺] (f IÏ) ⍵ 2 | ⍺←⊢ 3 | 0::(⊃⍬⍴⎕DM)⎕SIGNAL ⎕EN 4 | ⍺(⍺⍺ #.isolate.ynys.llEach)⍵ 5 | } 6 | -------------------------------------------------------------------------------- /Source/ynys/ynys.dyalog: -------------------------------------------------------------------------------- 1 | ynys←{⍺←⊢ 2 | ⍝ ynys - Welsh - island - cognate with insular, isolate &c. 3 | ⍝ and beginning with "y" it's at the bottom of autocomplete 4 | } 5 | -------------------------------------------------------------------------------- /Source/isolate/Available.dyalog: -------------------------------------------------------------------------------- 1 | Available←{ ⍝ 1 if an item of ⍵ has been computed, 0 if it is still a future 2 | 0::(⊃⍬⍴⎕DM)⎕SIGNAL ⎕EN 3 | ynys.Available ⍵ 4 | } 5 | -------------------------------------------------------------------------------- /Source/root/IIKey.dyalog: -------------------------------------------------------------------------------- 1 | IIÐ←{ ⍝ Model of parallel key 2 | ⍺←⊢ 3 | 0::(⊃⍬⍴⎕DM)⎕SIGNAL ⎕EN ⍝ Throw all errors 4 | ⍺(⍺⍺ #.isolate.ynys.llKey)⍵ 5 | } 6 | -------------------------------------------------------------------------------- /Source/ynys/checkPortIsFree.dyalog: -------------------------------------------------------------------------------- 1 | r←checkPortIsFree n;z 2 | ⍝ Check that TCP port is currently free 3 | :If r←0=⊃z←DRC.Srv'' ''n 4 | z←DRC.Close 1⊃z 5 | :EndIf 6 | -------------------------------------------------------------------------------- /Source/isolate/Running.dyalog: -------------------------------------------------------------------------------- 1 | Running←{ ⍝ 1 for items of ⍵ which are still futures which are being computed, else 0 2 | 0::(⊃⍬⍴⎕DM)⎕SIGNAL ⎕EN 3 | ynys.Running ⍵ 4 | } 5 | -------------------------------------------------------------------------------- /Source/root/New.dyalog: -------------------------------------------------------------------------------- 1 | ø←{ ⍝ Model of [currency sign] (create isolate) 2 | ⍺←⊢ 3 | 0::(⊃⍬⍴⎕DM)⎕SIGNAL ⎕EN ⍝ Throw all errors 4 | ⍺(#.isolate.ynys.New)⍵ 5 | } 6 | -------------------------------------------------------------------------------- /Source/isolate/StartServer.dyalog: -------------------------------------------------------------------------------- 1 | StartServer←{ ⍝ Start an Isolate Server (not usually called by application code) 2 | 0::(⊃⍬⍴⎕DM)⎕SIGNAL ⎕EN 3 | ynys.StartServer ⍵ 4 | } 5 | -------------------------------------------------------------------------------- /Source/ynys/qsignal.dyalog: -------------------------------------------------------------------------------- 1 | r←x qsignal y 2 | ⍝ To help signal an error that will not terminate a dfn capsule 3 | y←(y 86)[y>999] ⍝ Use 86 for interrupts and CONGA ERRORS 4 | ⎕SIGNAL⊂('EN'y)('EM'x)('Message'x) 5 | -------------------------------------------------------------------------------- /Source/root/II.dyalog: -------------------------------------------------------------------------------- 1 | II←{ ⍝ Model of parallel operator: [⍺] (f II) ⍵ 2 | 0::(⊃⍬⍴⎕DM)⎕SIGNAL ⎕EN ⍝ Throw all errors 3 | 0=⎕NC'⍺':(⍺⍺ #.isolate.ynys.ll)⍵ 4 | ⍺(⍺⍺ #.isolate.ynys.ll)⍵ 5 | } 6 | -------------------------------------------------------------------------------- /Source/ynys/callerSpace.dyalog: -------------------------------------------------------------------------------- 1 | callerSpace←{⍺←⊢ 2 | ⍬⍴((0,⎕RSI)~0,⎕THIS,##,⍵),# 3 | ⍝ caller excluding this space and the main isolate method-space above it. 4 | ⍝ none of the code above is redundant. 2014-01-09 5 | } 6 | -------------------------------------------------------------------------------- /Source/isolate/Values.dyalog: -------------------------------------------------------------------------------- 1 | Values←{ ⍝ Return computed values in ⍵, return ⍺ in place of futures (or ⎕NULL if ⍺ not specified) 2 | ⍺←⊢ 3 | 0::(⊃⍬⍴⎕DM)⎕SIGNAL ⎕EN 4 | ⍵≡⍺ ⍵:ynys.Values⊢⍵ 5 | ⍺(ynys.Values)⍵ 6 | } 7 | -------------------------------------------------------------------------------- /Source/ynys/getDefaultWS.dyalog: -------------------------------------------------------------------------------- 1 | getDefaultWS←{ 2 | win←'W'≡⊃3⊃#.⎕WG'APLVersion' 3 | ∨/('/\'[win],⍵)⍷⎕WSID:⎕WSID 4 | ⍵ ⍝ ⍵ is normally 'isolate' 5 | ⍝ use exact current ws if looks like an isolate development ws 6 | } 7 | -------------------------------------------------------------------------------- /Source/ynys/validateRemoteFilters.dyalog: -------------------------------------------------------------------------------- 1 | validateRemoteFilters←{ 2 | 0=⍴⍵:⍵ ⍝ Validate filters and return nested vector 3 | z←{1↓¨(','=⍵)⊂⍵}',',⍵~' ' ⍝ Split on comma 4 | 0∊(3↑¨z)∊'ip=' 'IP=':'INVALID FILTERS'⎕SIGNAL 11 5 | z 6 | } 7 | -------------------------------------------------------------------------------- /Tests/FindWs.dyalog: -------------------------------------------------------------------------------- 1 | r←FindWs ws;paths;sep 2 | sep←(1+'W'=⊃⊃'.'⎕WG'APLVersion')⊃':;' 3 | paths←1↓¨{(1+sep=⍵)⊆⍵}sep,2 ⎕NQ'.' 'GetEnvironment' 'WSPATH' 4 | paths,←(2 ⎕NQ'.' 'GetEnvironment' 'DYALOG'),'/ws' 5 | r←{(1⍳⍨⎕NEXISTS¨⍵)⊃⍵,⊂''}paths,¨⊂'/',ws 6 | -------------------------------------------------------------------------------- /Source/ynys/Description.dyalog: -------------------------------------------------------------------------------- 1 | r←Description 2 | r←'' 'For information on how to use Futures and Isolates,' 3 | r,←⊂'see the manual "Parallel Language Features",' 4 | r,←⊂'which can be found in the Dyalog Documentation Centre' 5 | r,←⊂'at http://docs.dyalog.com' 6 | r←⍪r 7 | -------------------------------------------------------------------------------- /Source/ynys/InitConnections.dyalog: -------------------------------------------------------------------------------- 1 | r←pids InitConnections(addr ports id remote);i 2 | ⍝ Establish connections to all new processes 3 | r←(≢pids)⍴⊂'' 4 | :For i :In ⍳≢pids 5 | :Trap 0 6 | (i⊃r)←(i⊃pids)InitConnection addr(i⊃ports)id remote 7 | :EndTrap 8 | :EndFor 9 | -------------------------------------------------------------------------------- /Tests/all.dyalogtest: -------------------------------------------------------------------------------- 1 | DyalogTest : 1.25 2 | ID : Isolate Tests 3 | Description: Put Isolates through their paces 4 | 5 | Setup : setup 6 | Teardown: teardown 7 | Test : test_runtime 8 | Test : test_basic 9 | Test : test_syntax 10 | Test : test_errors 11 | Test : test_callbacks -------------------------------------------------------------------------------- /Source/ynys/addWSpath.dyalog: -------------------------------------------------------------------------------- 1 | addWSpath←{⍺←⊢ 2 | ∨/'/\'∊ws←⍵:⊢ws ⍝ assume extant path is good 3 | dir←##.RPCServer.GetEnv'DYALOG' 4 | sep←⊃'/\'∩dir 5 | dir,←sep~⊢/dir ⍝ append sep if needs 6 | dir,'ws',sep,ws ⍝ WS folder 7 | ⍝ add ...dyalog/ws/ to ws if no path spec'd 8 | } 9 | -------------------------------------------------------------------------------- /Source/ynys/checkWs.dyalog: -------------------------------------------------------------------------------- 1 | checkWs←{⍺←⊢ 2 | ⍝ ⍵ IS a string 3 | z←⎕NS'' 4 | 0::'WS NOT FOUND'⎕SIGNAL 11 ⍝ any error bar Value 5 | 6::⍵ ⍝ value error implies copy ok 6 | z←{}'⎕IO'z.⎕CY ⍵ ⍝ force value error → return ⍵ 7 | ⍝ 8 | } 9 | -------------------------------------------------------------------------------- /Source/ynys/dix.dyalog: -------------------------------------------------------------------------------- 1 | dix←{⍺←⊢ 2 | r←(⍺⊣#).⎕NS'' 3 | r⊣r.{⍎⍕'(',⍺,')←⍵'}/⍵ 4 | ⍝ dictionary 5 | ⍝ [⍺] container space for dictionary - dflt: # 6 | ⍝ ⍵ names values 7 | ⍝ names ' '-del'd or nested list 8 | ⍝ values conformable with names 9 | ⍝ e.g. pt←(dix'⎕ml'3).⊂ 10 | } 11 | -------------------------------------------------------------------------------- /Source/ynys/publicMethods.dyalog: -------------------------------------------------------------------------------- 1 | publicMethods←{⍺←⊢ 2 | messages'⍝- ' 3 | ⍝- AddServer 4 | ⍝- Config 5 | ⍝- InternalState 6 | ⍝- LastError 7 | ⍝- New 8 | ⍝- StartServer 9 | ⍝- RemoveServer 10 | ⍝- Stats 11 | ⍝- ll 12 | ⍝- llEach 13 | ⍝- llKey 14 | ⍝- llOuter 15 | ⍝- llRank 16 | 17 | ⍝ add more after prefix '⍝- ' 18 | } 19 | -------------------------------------------------------------------------------- /Source/ynys/ll.dyalog: -------------------------------------------------------------------------------- 1 | ll←{ 2 | z←Init 1 3 | trapErr''::signal'' 4 | s←⍺⍺ fnSpace'f' 5 | 6 | i←New s 7 | 0=⎕NC'⍺':i.f ⍵ 8 | ⍺ i.f ⍵ 9 | 10 | ⍝ parallel 11 | ⍝ ⍺ [larg] 12 | ⍝ ⍺⍺ [fn to apply to or between [⍺] and or ⍵ 13 | ⍝ ⍵ rarg 14 | ⍝ ← result of running ⍺⍺ in a parallel process. 15 | } 16 | -------------------------------------------------------------------------------- /Source/ynys/sessionStart.dyalog: -------------------------------------------------------------------------------- 1 | sessionStart←{⍺←⊢ 2 | 24 60 60 1000{(dateNo ⍵)-(⍺-⍺⍺⊥3↓⍵)÷×/⍺⍺}/(2⊃⎕AI)⎕TS 3 | ⍝ diff twixt ⎕ts and ⎕ai 4 | ⍝ ← number of days and the decimal part thereof after the start of the last 5 | ⍝ day of the penultimate year of the nineteenth century: 1899-12-31 00.00 6 | ⍝ immune to system clock change as both rely on that. 7 | } 8 | -------------------------------------------------------------------------------- /Source/ynys/checkLocalServer.dyalog: -------------------------------------------------------------------------------- 1 | checkLocalServer w;z 2 | ⍝ take opportunity to check listener is up 3 | :If options.listen 4 | :If 2≠⎕NC'session.listeningtid' 5 | :OrIf session.listeningtid(~∊)⎕TNUMS 6 | ⎕←'ISOLATE: Callback server restarted' 7 | onerror←options.onerror 8 | z←localServer 1 9 | :EndIf 10 | :EndIf 11 | -------------------------------------------------------------------------------- /Source/ynys/SendWait.dyalog: -------------------------------------------------------------------------------- 1 | r←SendWait(r cmd);z 2 | ⍝ Used by InitConnection for first 2 transactions with newly started process 3 | :If 0=⊃z←DRC.Send r cmd 4 | :AndIf 0=⊃z←DRC.Wait r 30000 5 | :If 0 'Receive'≡z[0 2] 6 | r←3 1⊃z 7 | :EndIf 8 | :Else 9 | {}DRC.Close r 10 | ('ISOLATE: New process did not respond to handshake:',,⍕z)⎕SIGNAL 11 11 | :EndIf 12 | -------------------------------------------------------------------------------- /Source/ynys/New.dyalog: -------------------------------------------------------------------------------- 1 | New←{⍺←⊢ 2 | z←Init 1 3 | trapErr''::signal'' 4 | caller←callerSpace'' 5 | source←caller argType ⍵ 6 | id←⍬ isolates source 7 | proxy←caller.⎕NS proxyClone 8 | proxy.(iSpace iD iCarus)←iSpace id,suicide.New'cleanup'id 9 | z←proxy.⎕DF(⍕caller),'.[isolate]' 10 | z←1(700⌶)proxy 11 | 1:proxy 12 | ⍝ simulate isolate primitive: UCS 164 / sol 13 | } 14 | -------------------------------------------------------------------------------- /Source/ynys/whoami.dyalog: -------------------------------------------------------------------------------- 1 | whoami←{⍺←⊢ 2 | 'localhost' 3 | (W wc wa L lc la A ac aa)←messages'⍝- ' 4 | (adr cfg)←(W L A⍳⊂3⍴⊃#.⎕WG'APLVersion')⊃(wa wc)(la lc)(aa ac) 5 | ⊃{(⍵≠' ')⊂⍵⊣⎕ML←3}(1↓⍳∘':'↓⊢)⊃{⍵⌿⍨∨/adr⍷minuscule↑⍵}⎕CMD cfg 6 | ⍝- Win 7 | ⍝- ipconfig 8 | ⍝- ipv4 address 9 | 10 | ⍝- Lin 11 | ⍝- /sbin/ifconfig 12 | ⍝- inet addr 13 | 14 | ⍝- AIX 15 | ⍝- ifconfig 16 | ⍝- inet addr 17 | } 18 | -------------------------------------------------------------------------------- /Source/ynys/llEach.dyalog: -------------------------------------------------------------------------------- 1 | llEach←{⍺←⊢ 2 | z←Init 1 3 | trapErr''::signal'' 4 | n←⍺⍺ fnSpace'f' 5 | s←⍴⍺⊢¨⍵ ⍝ Scalar extension 6 | i←New¨s⍴n 7 | ⍺ i.f ⍵ 8 | 9 | ⍝ parallel each 10 | ⍝ ⍺ [larg] 11 | ⍝ ⍺⍺ fn to apply to or between corresponding items of [⍺] and/or ⍵ 12 | ⍝ ⍵ rarg 13 | ⍝ ← results (which may be futures) of running ⍺⍺ in each 14 | ⍝ of one or more ephemeral isolates 15 | } 16 | -------------------------------------------------------------------------------- /Source/ynys/llOuter.dyalog: -------------------------------------------------------------------------------- 1 | llOuter←{⍺←'VALENCE ERROR'⎕SIGNAL 6 2 | z←Init 1 3 | trapErr''::signal'' 4 | (⍉(⌽s)⍴⍉⍺)⍺⍺ llEach ⍵⍴⍨s←(⍴⍺),⍴⍵ 5 | 6 | ⍝ s←,⍴a←∪,⍺ 7 | ⍝ s,←⍴w←∪,⍵ 8 | ⍝ r←(⍉(⌽s)⍴⍉a)⍺⍺ llEach s⍴w 9 | ⍝ 1:r[a⍳⍺;w⍳⍵] 10 | 11 | ⍝ parallel outer product 12 | ⍝ ⍺ array 13 | ⍝ ⍺⍺ fn to apply between items of ⍺ and ⍵ 14 | ⍝ ⍵ array 15 | ⍝ ← aray of futures from ⍺⍺ applied between 16 | ⍝ each pair of items selected from ⍺ and ⍵ 17 | } 18 | -------------------------------------------------------------------------------- /isolate.dyalogbuild: -------------------------------------------------------------------------------- 1 | DyalogBuild: 0.1 2 | ID : isolate, Version=1.5 3 | Description: isolate workspace for Dyalog v17.1 4 | Defaults : (⎕IO ⎕ML ⎕WX)←1 1 3 5 | TARGET : isolate.dws 6 | LX : #.isolate.ynys.isoStart ⍬ 7 | 8 | EXEC : ⎕EX '#.ll' '#.isolate' 9 | 10 | NS : Source/root/*.dyalog, Target=# 11 | NS : Source/isolate/*.dyalog, Target=#.isolate 12 | NS : Source/ynys/*.dyalog, Target=#.isolate.ynys 13 | 14 | NS : Build/*.dyalog, Target=# 15 | EXEC : #.Build 16 | EXEC : ⎕EX 'Build' 17 | -------------------------------------------------------------------------------- /Tests/expect.dyalog: -------------------------------------------------------------------------------- 1 | msgs expect expr;z;got;dmx 2 | ⍝ Check that the expected error was receive 3 | 4 | :Trap 0 ⍝ FUTURE ERROR 5 | z←+⍎expr ⍝ force future to materialize 6 | ('Did not fail as expected: ',expr)Fail 1 7 | 8 | :Else ⍝ We are expecting a VALUE ERROR 9 | got←⎕DMX.Message 10 | ⍝ got,←(0=≢got)/⎕DMX.EM 11 | :If ((∊⍕¨msgs)~': '){⍺≢(⍴⍺)↑⍵}got~': ' 12 | 'expected' 'got'⍪⍉⍪msgs got 13 | ⎕TRAP←0 'S' 14 | ∘ 15 | ⎕SIGNAL something 16 | :EndIf 17 | :EndTrap 18 | -------------------------------------------------------------------------------- /Source/ynys/newSession.dyalog: -------------------------------------------------------------------------------- 1 | r←newSession w;z 2 | :If 0=⎕NC'session.started' 3 | :OrIf 0.0001<|session.started-sessionStart'' 4 | r←1 5 | :Else ⋄ r←0 ⍝ not a new session 6 | session.listen←options.listen 7 | checkLocalServer ⍬ 8 | :EndIf 9 | 10 | ⍝ The session is new if session.started is missing. 11 | ⍝ It needs to be restarted if session.started differs from the actual 12 | ⍝ start of the session by more than 8.64 seconds (1/10000 of a day) 13 | ⍝ that indicates that the ws was saved with the session space intact 14 | -------------------------------------------------------------------------------- /Tests/test_callbacks.dyalog: -------------------------------------------------------------------------------- 1 | z←test_callbacks dummy;counts;is 2 | ⍝ test that calls back to the parent workspace are working 3 | 4 | {}#.isolate.Config'listen' 1 5 | {}#.isolate.Reset 0 6 | 7 | is←#.ø'' 8 | 9 | :Trap 11 ⋄ 'is.##.NNNN' Fail ⎕NULL Check is.##.COUNTER ⍝ This should DOMAIN ERROR 10 | :EndTrap 11 | 12 | #.COUNTER←1 13 | 14 | counts←⊃¨{##.COUNTER+⍵}#.IÏ⍳4 15 | 'Callback counter test' Fail 2 3 4 5 Check counts 16 | 17 | #.⎕EX 'COUNTER' 18 | {}#.isolate.Config'listen' 0 19 | {}#.isolate.Reset 0 20 | 21 | z←'' 22 | -------------------------------------------------------------------------------- /Source/ynys/cleanDM.dyalog: -------------------------------------------------------------------------------- 1 | r←cleanDM r;t;msg;line;caret;m 2 | →(0=⊃r)⍴0 ⍝ Not an error 3 | →(3≠⍴t←1⊃r)⍴0 ⍝ Not a ⎕DM 4 | (msg line caret)←t 5 | msg←('⍎'=⊃msg)↓msg 6 | :If 'f[] f←'≡6↑line ⋄ (line caret)←6↓¨line caret 7 | :ElseIf 'decode['≡7↑line 8 | :If ∨/':Case'⍷line ⋄ line←caret←'' 9 | :ElseIf ∨/m←'c⌷[d]where.'⍷line 10 | (line caret)←(11+m⍳1)↓¨line caret 11 | line←((⌊/line⍳') ')↑line),'[...]',('←'∊line)/'←...' 12 | :Else ⋄ (line caret)←(1+line⍳']')↓¨line caret 13 | :EndIf 14 | :EndIf 15 | (1⊃r)←msg line caret 16 | -------------------------------------------------------------------------------- /Source/ynys/connect.dyalog: -------------------------------------------------------------------------------- 1 | r←connect(chrid host port data);count 2 | :If 0=⊃r←DRCClt chrid host port ⍝ DRCClt will retry 3 | :AndIf 0=⊃r←DRC.Send chrid data ⍝ on any 4 | :AndIf 0=⊃r←DRC.Wait(1⊃r)20000 ⍝ error 5 | :AndIf 'Timeout'≢3⊃r ⍝ eventmode timeout 6 | :Else 7 | {}DRC.Close chrid 8 | ('ISOLATE: Connection to ',host,':',(⍕port),' failed: ',,⍕r)qsignal 6 9 | :EndIf 10 | ⍝ connect and send Initial payload 11 | ⍝ ⍺ attempts 12 | ⍝ ⍵ client-id ip-port data 13 | ⍝ data function and argument 14 | ⍝ ← final return from DRC 15 | -------------------------------------------------------------------------------- /Source/ynys/Config.dyalog: -------------------------------------------------------------------------------- 1 | Config←{⍺←⊢ 2 | newSession'':{ ⍝ else Init has already run 3 | 0::(⊃⎕DMX.DM)⎕SIGNAL ⎕DMX.EN 4 | setDefaults ⍵ 5 | }⍵ 6 | trapErr''::signal'' 7 | getSet ⍵ 8 | 9 | ⍝ set or query single option 10 | ⍝ ⍵ '' | name | name value 11 | ⍝ name one of params defined in setDefaults 12 | ⍝ value new value for param 13 | ⍝ ← ⍵:'' : table of all names and values 14 | ⍝ ⍵:name : value 15 | ⍝ ⍵:name value : old value having set new in param 16 | } 17 | -------------------------------------------------------------------------------- /Source/ynys/State.dyalog: -------------------------------------------------------------------------------- 1 | r←State dummy;counts 2 | ⍝ Return current process & isolate state 3 | 4 | :If 9=⎕NC'session' 5 | :If 0≠≢session.procs 6 | counts←session.assoc.(proc{⍺,(+/⍵),+/~⍵}⌸busy) 7 | r←session.procs[;2 3] 8 | r,←{⍵,+/⍵}(counts⍪0)[counts[;0]⍳session.procs[;0];1 2] 9 | r[(0,2≡/r[;0])/⍳1↑⍴r;0]←⊂'' 10 | r←({⍵,[¯0.5](≢¨⍵)⍴¨'-'}'Host' 'Port' 'Busy' 'Idle' 'Isolates')⍪r 11 | r←r[;0 1 4 2] 12 | :Else 13 | r←'[no servers defined]' 14 | :EndIf 15 | :Else 16 | r←'[not initialised]' 17 | :EndIf 18 | -------------------------------------------------------------------------------- /Source/ynys/until.dyalog: -------------------------------------------------------------------------------- 1 | until←{⍺←⊢ ⋄ f←⍺⍺ ⋄ t←⍵⍵ 2 | ⍵≡⍺ ⍵:f⍣(t⊣)⍵ 3 | ⍺{ ⍝ keep recursion local 4 | (⍺-1)∘∇⍣((⍺>1)>t z)⊢z←f ⍵ 5 | }⍵ 6 | ⍝ ⍺ [max] 7 | ⍝ ⍺⍺ monadic fn on arg - may be composed: (const∘function) 8 | ⍝ ⍵⍵ monadic test on arg or subsequent results 9 | ⍝ ⍵ arg 10 | ⍝ apply fn [up to max times] until test result 11 | ⍝ ← last application of fn 12 | ⍝ e.g. 13 | ⍝ 10 +∘1 until (≥∘5) 0 14 | ⍝ 5 15 | ⍝ 10 +∘1 until (≥∘15) 0 16 | ⍝ 10 17 | ⍝ +∘1 until (≥∘5) 0 18 | ⍝ 5 19 | ⍝ +∘1 until (≥∘15) 0 20 | ⍝ 15 21 | } 22 | -------------------------------------------------------------------------------- /Source/ynys/argType.dyalog: -------------------------------------------------------------------------------- 1 | argType←{⍺←⊢ 2 | trapErr''::signal'' 3 | (0∊⍴)⍵:⍺.⎕NS'' ⍝ empty 4 | (0=≡)and{9=⎕NC'⍵'}⍵:⍵ ⍝ ns 5 | (1=≡)and(''≡0⍴⊢)⍵:checkWs ⍵ 6 | (2=≡)and(''≡0⍴⊃)nl←,¨⍵:⍺.⎕NS↑nl ⍝ nl 7 | ⎕SIGNAL 11 8 | ⍝ ⍺ caller 9 | ⍝ ⍵ arg to new - empty | space | namelist | string 10 | ⍝ ← : 11 | ⍝ arg | res 12 | ⍝ --- + --- 13 | ⍝ empty | empty ns 14 | ⍝ space | clone ns 15 | ⍝ list | ns containing named fns 16 | ⍝ string | validated wsid 17 | } 18 | -------------------------------------------------------------------------------- /Source/ynys/getDRC.dyalog: -------------------------------------------------------------------------------- 1 | r←getDRC ref;ws 2 | :If ref≠# ⋄ r←ref ⍝ Not set, so we must create it 3 | :ElseIf 9=#.⎕NC'DRC' ⋄ r←#.DRC ⍝ in # already? 4 | :Else 5 | ws←'conga.dws' 6 | :Trap 0 7 | 'Conga'#.⎕CY ws ⍝ See if the interpreter can find it 8 | :Else 9 | ws←addWSpath'conga.dws' ⍝ Actually adds [DYALOG]/ws 10 | :Trap 0 11 | 'Conga'#.⎕CY ws 12 | :Else 13 | 'Unable to locate conga.dws'⎕SIGNAL 11 14 | :EndTrap 15 | :EndTrap 16 | #.DRC←#.Conga.Init'isolate' 17 | r←#.DRC 18 | :EndIf 19 | -------------------------------------------------------------------------------- /Source/ynys/derv.dyalog: -------------------------------------------------------------------------------- 1 | derv←{⍺←⊢ 2 | ⍕{ 3 | ⊃,/{ 4 | 0=≡⍵:⍵ 5 | 0∊⍴⍵:((''⍬ ⍵)⍳⊂⍵)⊃'''''' '⍬'⍵ 6 | ⍝ 1=≡⍵:'(',,∘')'⊃,/(⊂(' ',,⍵)⍳,⍵)⌷(⊂''' '' '),,⍵ 7 | 1=≡⍵:'(',,∘')'⊃,/,⍵ 8 | ⊃,/'(',,∘')'∇¨⍵ 9 | }⍵ 10 | }⎕CR(f←⍺⍺)/'f' 11 | ⍝ ⍺⍺ derv 12 | ⍝ ← executable text-string representation of ⍺⍺ 13 | ⍝ sometimes adds too many parens but would need MUCH more analysis not to 14 | ⍝ handles scalar nums, ' ', '' & ⍬ but not arrays in general 15 | ⍝ e.g. 16 | ⍝ sec ← (⊢⊣)/∘⍳∘(15E5∘×) 17 | ⍝ sec derv 0 18 | ⍝ ((((⊢⊣)/)∘⍳)∘( 1500000 ∘×)) 19 | } 20 | -------------------------------------------------------------------------------- /Tests/test_homeport.dyalog: -------------------------------------------------------------------------------- 1 | z←test_homeport dummy;test;double;ports;homeportmax;homeport 2 | ⍝ test fix for GitHub issue 12 - homeport not being assigned from config 3 | {}#.isolate.Config'homeport'(homeport←23232) 4 | {}#.isolate.Config'homeportmax'(homeportmax←23332) 5 | {}#.isolate.Config'processors' 4 6 | {}#.isolate.Reset 0 7 | test←'homeport test' 8 | double←{⍵+⍵}#.IÏ⍳4 9 | ⎕DL 0.5 10 | :Trap 0 11 | ports←{2 4⊃#.DRC.GetProp(⊃⊃⍵)'PeerAddr'}¨2 2⊃#.DRC.Tree'.' 12 | 'isolate not using Config homeport'Fail 1∧.=(homeport,1+homeportmax)⍸ports 13 | :Else 14 | ((⊃⎕DM),' in test_issue12 ')Fail 0 15 | :EndTrap 16 | z←'' 17 | -------------------------------------------------------------------------------- /Source/ynys/DRCClt.dyalog: -------------------------------------------------------------------------------- 1 | r←DRCClt args;count 2 | ⍝ Create a DRC Client, looping a bit 3 | 4 | {}DRC.Close⊃args ⍝ Paranoia, the bug is somewhere else, sorry! 5 | count←0 6 | :While 0≠⊃r←DRC.Clt args ⍝ Cannot connect 7 | :Select ⊃r 8 | :Case 1106 9 | ('ISOLATE: Unable to resolve or parse hostname: ',⍕args)⎕SIGNAL 11 10 | :CaseList 61 111 1111 10061 ⍝ Connection Refused is 61 on macOS, 10061 under Windows 11 | {}⎕DL session.retry_interval×count+←1 ⍝ longer wait each time 12 | :Else 13 | ('ISOLATE: Unable to connect to isolate process: ',⍕args)⎕SIGNAL 11 14 | :EndSelect 15 | :Until count≥session.retry_limit 16 | -------------------------------------------------------------------------------- /Source/ynys/prof.dyalog: -------------------------------------------------------------------------------- 1 | prof←{⍺←⊢ ⋄ ⎕IO←0 2 | z←⎕PROFILE'clear' 3 | z←⎕PROFILE'start' 4 | r←⍺ ⍺⍺⍣⍵⍵⊢⍵ 5 | z←⎕PROFILE'stop' ⋄ e←⍬⍴⎕LC 6 | p←e↓⎕PROFILE'data' 7 | p[;3 4]{⌊0.5+100×⍺÷⍵}←0 4⌷p 8 | p[;]←p[⍒p[;3];] 9 | n←{⍺,~∘' '⍕,'[',(⍪⍵),']'}/2↑⍤1⊢p 10 | p←4↑⍤1⊢n,0 2↓p 11 | p⍪⍨←'operation[]' 'called' 'exclusive %' 'inclusive %' 12 | r p 13 | ⍝ ⍺ [larg] 14 | ⍝ ⍺⍺ fn 15 | ⍝ ⍵⍵ rop to ⍣ (fn or int) 16 | ⍝ ⍵ rarg 17 | ⍝ ← res prof 18 | ⍝ res result of: ⍺ ⍺⍺⍣⍵⍵⊢⍵ 19 | ⍝ prof ⎕profile wherein 20 | ⍝ cols[0,1] joined as fnname[lineno] 21 | ⍝ times converted to % so ⍺⍺ takes 100 overall 22 | ⍝ last two cols missing 23 | } 24 | -------------------------------------------------------------------------------- /Source/ynys/InitConnection.dyalog: -------------------------------------------------------------------------------- 1 | r←pid InitConnection(addr port id remote);z;ok 2 | ⍝ Establish connection to new process, 3 | ⍝ Verify it's identity and configure it 4 | ⍝ Return CONGA client name or '' on failure 5 | 6 | ok←0 7 | :If 0≠⊃z←DRCClt('PROC',⍕pid)addr port 8 | ('ISOLATE: Unable to connect to ',addr,':',(⍕port),':',,⍕z)⎕SIGNAL 11 9 | :Else ⍝ Connection made 10 | r←1⊃z 11 | ok←id∊¯1,1⊃SendWait r('#.isolate.ynys.execute'('' 'identify')) 12 | :If id≠¯1 ⍝ Only set remote access if we started the process 13 | ok←(0=⍴remote)∨remote≡SendWait r('AllowRemoteAccess'remote) 14 | :EndIf 15 | 16 | :If ~ok 17 | r←''⊣DRC.Close r 18 | :EndIf 19 | :EndIf 20 | -------------------------------------------------------------------------------- /Source/ynys/llKey.dyalog: -------------------------------------------------------------------------------- 1 | llKey←{⍺←⊢ ⍝ key operator 2 | z←Init 1 3 | trapErr''::signal'' 4 | ⍵≡⍺ ⍵:⍵ ∇(callerSpace'').⍳≢⍵ 5 | 6 | j←⍋i←{(∪⍳⊢)↓⍣(≢1↓⍴⍵)⊢⍵}⍺ 7 | (⊂⍤¯1⊢((⍳⍴i)=i⍳i)⌿⍺)⍺⍺ llEach(2≠/¯1,i[j])⊂[0](⊂j)⌷⍵ 8 | 9 | ⍝ parallel key : ⍺ ⍺⍺ llkey ⍵ 10 | ⍝ ⍺ [larg] - array (≢⍺) ≡ ≢⍵ 11 | ⍝ ⍺⍺ llKey ⍵ ←→ ⍵ ⍺⍺ llKey ⍳≢⍵ 12 | ⍝ ⍺⍺ fn to apply between each unique major cell of ⍺ and 13 | ⍝ the corresponding subarray of ⍵ ; or to the latter. 14 | ⍝ ⍵ rarg - array 15 | ⍝ ← futures array - results of each application of ⍺⍺ 16 | ⍝ to emulate primitive key completely it should mix (↑) the results. 17 | ⍝ This CANNOT BE DONE here as it would dereference the futures. 18 | ⍝ Phil Last ⍝ 2007-06-22 22:57 19 | } 20 | -------------------------------------------------------------------------------- /Tests/test_runtime.dyalog: -------------------------------------------------------------------------------- 1 | z←test_runtime dummy;fail;ns;is;n;curex;rt;ver;curver;old 2 | ⍝ Test all possible settings of the "runtime" option 3 | ⍝ 0 = use development environment 4 | ⍝ 1 = use runtime interpreter 5 | ⍝ charvec = load named executable 6 | 7 | old←#.isolate.Config'processors' 1 8 | 9 | curex←#.isolate.APLProcess.GetCurrentExecutable 10 | curver←4⊃'.'⎕WG'APLVersion' 11 | 12 | :For (rt ver) :In (0 'Development')(1 'Runtime')(curex curver) 13 | 14 | {}#.isolate.Config'runtime'rt 15 | {}#.isolate.Reset 0 16 | is←#.isolate.New'' 17 | ('runtime=',⍕rt)Fail ver Check is.(4⊃'.'⎕WG'APLVersion') 18 | ⎕EX'is' 19 | 20 | :EndFor 21 | 22 | {}#.isolate.Config'processors'old 23 | {}#.isolate.Config'runtime'1 24 | {}#.isolate.Reset 0 25 | 26 | z←'' 27 | -------------------------------------------------------------------------------- /Source/ynys/Stats.dyalog: -------------------------------------------------------------------------------- 1 | r←Stats dummy;n;stats;proc;z 2 | :If 9=⎕NC'session' 3 | :If 0≠n←≢session.procs 4 | stats←⍬ 5 | :For proc :In session.procs[;4] 6 | :If 0=⊃z←DRC.Send proc('ß' '') 7 | :AndIf 0=⊃z←DRC.Wait 1⊃z 8 | :AndIf 0=⊃z←3⊃z 9 | stats,←z[1] 10 | :Else 11 | stats,←⊂⍬ 12 | :EndIf 13 | :EndFor 14 | r←session.procs[;2 3] 15 | r[(0,2≡/r[;0])/⍳1↑⍴r;0]←⊂'' 16 | r,←↑stats 17 | r[;2]←↓'ZI4,<->,ZI2,<->,ZI2,< >,ZI2,<:>,ZI2,<:>,ZI2'⎕FMT 0 ¯1↓↑r[;2] 18 | r←({⍵,[¯0.5](≢¨⍵)⍴¨'-'}'Host' 'Port' 'Start' 'Cmds' 'Errs' 'CPU')⍪r 19 | :Else 20 | r←'[no servers defined]' 21 | :EndIf 22 | :Else 23 | r←'[not initialised]' 24 | :EndIf 25 | -------------------------------------------------------------------------------- /Source/ynys/llRank.dyalog: -------------------------------------------------------------------------------- 1 | llRank←{⍺←⊢ 2 | z←Init 1 3 | trapErr''::signal'' 4 | mlr←⌽3⍴⌽⍵⍵,⍬ 5 | m←⍵≡⍺ ⍵ 6 | l r←-1↓r⌊|l+r×0>l←(⊂⍒m×⍳3)⌷mlr⌊r←3⍴(⍴⍴⍵),⍴⍴⍺⊣0 7 | w←⊂[r↑⍳⍴⍴⍵]⍵ 8 | m:⍺⍺ llEach w ⍝ monad 9 | (⊂[l↑⍳⍴⍴⍺]⍺)⍺⍺ llEach w ⍝ dyad 10 | ⍝ parallel rank 11 | ⍝ ⍺ [larg] 12 | ⍝ ⍺⍺ fn to apply to or between corresponding cells of [⍺] and/or ⍵ 13 | ⍝ ⍵⍵ ranks (monadic, left, right) of cells of [⍺] and/or ⍵ 14 | ⍝ to or between which to apply ⍺⍺ 15 | ⍝ ⍵ rarg 16 | ⍝ ← results or futures from running ⍺⍺ in each of one 17 | ⍝ or more ephemeral isolates 18 | ⍝ to emulate primitive rank completely it should mix (↑) the results. 19 | ⍝ This CANNOT BE DONE here as it dereferences the futures. 20 | ⍝ Phil Last ⍝ 2007-06-22 22:57 21 | } 22 | -------------------------------------------------------------------------------- /Source/ynys/cleanup.dyalog: -------------------------------------------------------------------------------- 1 | cleanup←{⍺←⊢ 2 | trapErr''::signal'' 3 | (chrid port numid)←⍵.(chrid port numid) 4 | ⎕←⍪'.'/⍨options.debug 5 | (ns←⎕NS proxyClone).(iD iSpace)←⍵ iSpace ⍝ recreate temp proxy 6 | rem←{session.assoc.(iso proc busy⌿⍨←⊂iso≠⍵)} 7 | 11::rem numid ⍝ DRC reported errors 8 | z←ns.iSend{⍵(1('{#.⎕EX''',⍵,'''}')0)}chrid ⍝ expunge remote namespace 9 | z←DRC.Close chrid 10 | rem numid ⍝ remove numid from table 11 | 1: 12 | ⍝ called by destructor of suicide class when isolate proxy disappears. 13 | ⍝ ⍵ space: chrid port mumid 14 | ⍝ numid unique numeric identifier for isolate 15 | ⍝ chrid identifies DRC client and isolate space in remote process 16 | ⍝ port on which process is listening 17 | } 18 | -------------------------------------------------------------------------------- /Source/ynys/receive.dyalog: -------------------------------------------------------------------------------- 1 | receive←{⍺←⊢ 2 | (source listen id)←⍵ ⍝ this all happens remotely 3 | name←id.chrid 4 | root←⎕NS'' 5 | z←{root.⎕FX¨proxySpace.(⎕CR¨⎕NL ¯3),⊂⎕CR'tracelog'}⍣listen⊢1 ⍝ prepare for calls back 6 | root.iSpace←⎕THIS 7 | id.(port←home) 8 | id.(chrid←'Iso',⍕1+numid) 9 | id.tgt←,'#' 10 | root.iD←id 11 | iso←root.⎕NS(⍴⍴source)⊃source'' ⍝ clone of source 12 | z←iso.{6::0 ⋄ z←{}⎕CY ⍵}⍣(≡source)⊢source ⍝ or copy if ws 13 | z←iso.{6::0 ⋄ z←{}(↑'⎕io' '⎕ml')⎕CY ⍵}⍣(≡source)⊢source 14 | z←name{#.⍎⍺,'←⍵'}iso ⍝ name it in root 15 | z←#.DRC.Clt⍣listen⊢id.(chrid orig port) ⍝ orig=host if local 16 | 1⊣1(700⌶)root ⍝ Make isolate 17 | } 18 | -------------------------------------------------------------------------------- /Source/ynys/StartServer.dyalog: -------------------------------------------------------------------------------- 1 | StartServer←{⍺←⊢ 2 | msg←messages'⍝- ' ⍝ 3 | ~newSession'':(0⊃msg),' ',options.status 4 | z←Config'status' 'server' 5 | allowremote←validateRemoteFilters ⍵ 6 | 7 | z←allowremote Init 1 8 | 9 | address←##.APLProcess.MyDNSName 10 | addresses←##.RPCServer.DNSLookup address 11 | addresses←addresses[⍋↑addresses[;2];0 1] 12 | addresses←addresses[;0]{⊂⍺ ⍵}⌸0 1↓addresses 13 | 14 | ports←∪session.procs[;3] 15 | info←(1 2⊃¨⊂msg),⍪address ports 16 | res←{4<≢⍵:msg[4 5 6 7],⍪address(⊃⍵)(≢⍵)'' 17 | msg[4 5 7],⍪address((⊃⍵)+⍳≢⍵)''}ports 18 | res←∊⍕¨res 19 | ⎕←⍪,' ',⍪info(3⊃msg)res 20 | ⎕←⍪'' 'Full IP address list:' '' 21 | ⎕←addresses 22 | 1:'' 23 | 24 | ⍝- Session already started as 25 | ⍝- Machine Name: 26 | ⍝- IP Ports: 27 | ⍝- Enter the following in the client session: 28 | ⍝- #.isolate.AddServer ' 29 | ⍝- ' ( 30 | ⍝- -⎕IO-⍳ 31 | ⍝- ) 32 | } 33 | -------------------------------------------------------------------------------- /Source/ynys/suicide.dyalog: -------------------------------------------------------------------------------- 1 | :Class suicide 2 | ∇ inst←New data;whence 3 | :Access public shared 4 | whence←⍬⍴⎕RSI 5 | inst←whence.⎕NEW ⎕THIS 6 | inst.(whence data)←whence data 7 | ∇ 8 | 9 | ∇ coroner 10 | :Implements destructor 11 | :Trap 0 12 | (fn arg)←data 13 | (whence.⍎fn)arg 14 | :EndTrap 15 | ∇ 16 | ⍝ set a destructor on an ordinary container space. 17 | ⍝ When the space into which the instance is set, 18 | ⍝ e.g. 'this' in: 19 | ⍝ this.close←suicide.New name arg 20 | ⍝ is destroyed, the function named as the first of 21 | ⍝ two items in New's argument, that must be in the 22 | ⍝ space that called 'New', is called with the second 23 | ⍝ item of that same arg and can be made to release 24 | ⍝ resources even though space - 'this' has gone. 25 | ⍝ Phil Last ⍝ 2013-06-29 10:31 26 | :EndClass 27 | -------------------------------------------------------------------------------- /Tests/setup.dyalog: -------------------------------------------------------------------------------- 1 | r←setup dummy;do 2 | ⍝ Setup for isolate tests - reset any settings to defaults 3 | :If 0=#.⎕NC'isolate' 4 | :If 2=(1⊃⎕RSI).⎕NC'quiet' ⍝ we usually run this through ]DTest 5 | :AndIf 0=(1⊃⎕RSI).quiet ⍝ check if quiet-flag is set 6 | Log'Did not find #.isolate, now attempting to build (and save) the workspace' 7 | :EndIf 8 | ⎕←r←'Ending this run to launch ]DBuild. Will automatically resume afterwards!' 9 | do←{key←{2 ⎕NQ'⎕SE' 'Keypress'⍵} ⋄ key¨⍵,⊂'ER'} 10 | do ']DBuild ',(∊1 ⎕NPARTS(1⊃⎕NPARTS ##.TESTSOURCE),'../isolate.dyalogbuild'),' -clear ',((1⊃⎕RSI).quiet/' -quiet') 11 | do ')save' 12 | do ']',⎕SE.cmd 13 | →0 14 | :EndIf 15 | 16 | :If 0=⎕NC'Fail' ⍝ Running v16.0 or earlier 17 | ⎕FX'msg Fail value' 'msg ⎕SIGNAL (1∊value)/777' 18 | :EndIf 19 | 20 | {}#.isolate.Config'runtime' 1 21 | {}#.isolate.Config'onerror' 'signal' 22 | {}#.isolate.Config'processors' 4 23 | 24 | r←'' 25 | -------------------------------------------------------------------------------- /Source/ynys/fnSpace.dyalog: -------------------------------------------------------------------------------- 1 | fnSpace←{⍺←⊢ 2 | 3 | trapErr''::signal'' 4 | s←(callerSpace'').⎕NS'' 5 | N←,⍵ 6 | f←⍺⍺ 7 | c←⎕CR'f' 8 | q←'#'≡⊃⊃c ⍝ qualified 9 | c←⊃∘⌽⍣q⊢c ⍝ remove qualification 10 | d←'{'≡⍬⍴c ⍝ anon dfn 11 | t←1=≢⍴c ⍝ tacit derv 12 | n←s.⎕FX{,↓⍣(1=≡⍵){N,'←',1↓,'⋄',⍵}⍣d⊢⍵}c 13 | ⍝ name anon dfn as N 14 | z←s.⎕FX(,↓N,'←{⍺←⊢ ⋄ ⍺',n,'⍵}')/⍨~(⊂n)∊N 0 1 15 | ⍝ if not N then N calls it 16 | z←s.⍎⍣t⊢N,'←',f derv⍣t⊢0 17 | 1:s 18 | ⍝ ⍺⍺ fn 19 | ⍝ ⍵ required name for fn in space 20 | ⍝ ← space child of caller containing fn as ⍵ 21 | ⍝ for use by ephemeral isolates in llEach &c. 22 | 23 | trapErr''::signal'' 24 | op←{} 25 | z←⎕FX,⊂'op←{⍵.',⍵,'←⍺⍺ ⋄ ⍵}' 26 | 1:⍺⍺ op(callerSpace'').⎕NS'' 27 | ⍝ possible alternative algorithm 2014-03-09 28 | ⍝ so far I can't find the reason I thought I needed 29 | ⍝ all the other stuff as this seems to do it ok. 30 | 31 | } 32 | -------------------------------------------------------------------------------- /Source/ynys/encode.dyalog: -------------------------------------------------------------------------------- 1 | encode←{⍺←⊢ 2 | la←(⍵≡⍺ ⍵)↓⊂⍺⊣0 ⍝ enclosed if supplied 3 | ra←(⊢⍴⍨1~⍨⍴)3↓⍵ ⍝ - empty if not 4 | (x nc s)←3⍴⍵ 5 | pargs←{⍺←⊢ ⍝ analyse [PropertyArguments] 6 | ra←⍵ 7 | 9≠⎕NC'ra':ra ⍝ not a space 8 | nms←'Indexers' 'IndexersSpecified' 'IndexOrigin' 'NewValue' 9 | 3>+/nms/⍨←∧\2=⌊ra.⎕NC nms:ra ⍝ not a [PropertyArgs] 10 | n←ra.⍎¨nms 11 | (0⊃n)←(-2⊃n)+(0,1⊃n)/0,0⊃n ⍝ 0+indices sans ⎕NULLs 12 | (1⊃n)←{⍵/⍳⍴⍵}1⊃n ⍝ 0+axes 13 | ((⍴n)↑1 1 0 1)/n 14 | } 15 | args←la,(⊂x),pargs ra 16 | code←nc{6|0(3 2)(3 3)(2 2)(2 3)(2 4)⍳⊂⍺,⍵}⍴args 17 | code,args 18 | ⍝ called in ##.proxyClone.iEvaluate - here because its inverse has to be 19 | ⍝ ⍺ larg to iEvaluate - larg to isolate.dyad 20 | ⍝ ⍵ rarg to iEvaluate - name class syntax [rarg | PropertyArguments] 21 | ⍝ creates list that encodes syntax and includes arguments 22 | } 23 | -------------------------------------------------------------------------------- /Tests/test_errors.dyalog: -------------------------------------------------------------------------------- 1 | z←test_errors dummy;x;result;is;start;max 2 | ⍝ test more advanced error handling 3 | 4 | {}#.isolate.Config'onerror' 'signal' ⍝ paranoia: should be the default 5 | {}#.isolate.Reset 0 6 | 7 | 'IÏ with one error'Fail 5 Check≢result←{z←⎕DL⊃⍵ ⋄ 1 2 3÷⍵}#.IÏ 1 2(3 4)(5 6)0 8 | 9 | start←3⊃⎕AI 10 | max←4000 11 | 12 | :Repeat 13 | :Until max<(3⊃⎕AI)-start 14 | 15 | x←4 5⍴#.isolate.(Values,Available,Failed,Running)'result' 16 | 'Test of isolate.Values'Fail((1 2 3)(0.5 1 1.5)⎕NULL ⎕NULL ⎕NULL)Check x[1;] 17 | 'Test of isolate.Available'Fail 1 1 0 0 0 Check x[2;] 18 | 'Test of isolate.Failed'Fail 0 0 1 0 1 Check x[3;] 19 | 'Test of isolate.Running'Fail 0 0 0 1 0 Check x[4;] 20 | 21 | :Repeat ⋄ ⎕DL 0.1 ⋄ :Until ~∨/#.isolate.Running'result' 22 | 5 'LENGTH ERROR' '{z←⎕DL⊃⍵ ⋄ 1 2 3÷⍵}'expect'3⊃result' 23 | 5 'LENGTH ERROR' '{z←⎕DL⊃⍵ ⋄ 1 2 3÷⍵}'expect'4⊃result' 24 | 11 'DOMAIN ERROR' '{z←⎕DL⊃⍵ ⋄ 1 2 3÷⍵}'expect'5⊃result' 25 | 26 | {}#.isolate.Reset 0 27 | 28 | z←'' 29 | -------------------------------------------------------------------------------- /Source/ynys/isoStart.dyalog: -------------------------------------------------------------------------------- 1 | isoStart←{⍺←⊢ 2 | ##.onerror←##.RPCServer.GetEnv'onerror' 3 | protocol←##.RPCServer.GetEnv'protocol' 4 | iso←'isolate' 5 | parm←##.RPCServer.GetEnv iso 6 | parm≢iso: Description 7 | msgbox←{ 8 | last←{⍺ ⍺⍺[(≢⍴⍵)-~⎕IO]⍵} 9 | ctr←{(⌊0.5×⍺-⍨⊢/⍴⍵)⌽⍺↑last ⍵} 10 | 'W'=⊃2↓'#'⎕WG'APLVersion':⎕DQ'msg'⎕WC'MsgBox'⍺ ⍵'Error' 11 | {⎕SM←2 7⍴⍺ 1 1 0 0 0 2059,⍵ 3 1 0 0 0 2059 ⋄ (⎕SM←0⌿⎕SM)⊢''⊣⎕SR 1 12 | }/⊃¨ctr/¨(2↑⍉⍪⍺){(⌈/≢∘⍉¨⍺ ⍵){⍺ ⍵}¨⍺ ⍵}↑⍵ 13 | } 14 | f00←{ 15 | 'R'∊⊃⊢/'#'⎕WG'APLVersion':{ 16 | Caption←'Isolate Startup Failure' 17 | Text←⎕DM 18 | Caption msgbox Text 19 | }'' 20 | (⊃⍬⍴⎕DMX.DM)⎕SIGNAL ⎕DMX.EN 21 | } 22 | ⎕TRAP←0 'C' 'f00⍬' 23 | 24 | ##.DRC←⎕THIS.DRC←getDRC # 25 | ##.RPCServer.SendProgress←0 ⍝ Do not send progress reports 26 | ##.RPCServer.Protocol←protocol 27 | ##.RPCServer.Boot 28 | ⍝ start as process if loaded with "isolate=isolate" in commandline 29 | } 30 | -------------------------------------------------------------------------------- /Source/ynys/AddServer.dyalog: -------------------------------------------------------------------------------- 1 | r←AddServer w;msg;addr;ports;z;ss;id;pclts;m;old;local 2 | msg←messages'⍝- ' 3 | :If 'server'≡Config'status' ⋄ r←0⊃msg ⋄ :Return ⋄ :EndIf 4 | :If local←''≡w 5 | addr←whoami'' 6 | :Else 7 | :If ''⍬≢0/¨w ⋄ r←1⊃msg ⋄ :Return ⋄ :EndIf 8 | (addr ports)←,¨w 9 | :If {1∊∊∘∊⍨⍵⊣⎕ML←0}addr ports ⋄ r←2⊃msg ⋄ :Return ⋄ :EndIf 10 | :EndIf 11 | z←Config'status' 'client' 12 | z←Init 0 13 | ss←session 14 | :If (⊂addr)∊ss.procs[;2] ⋄ r←(3⊃msg),' ',addr ⋄ :Return ⋄ :EndIf 15 | :If local 16 | ss.procs⍪←ss InitProcesses options 17 | :Else 18 | id←(⍳≢ports)+1+0⌈⌈/⊣/ss.procs 19 | ss.retry_limit←2⊣old←ss.retry_limit 20 | pclts←id InitConnections addr ports ¯1 ⍬ 21 | ss.retry_limit←old 22 | :If m←0∊≢¨pclts ⋄ r←(4⊃msg),' ',addr,': ',⍕m/ports ⋄ :Return ⋄ :EndIf 23 | ss.procs⍪←id,0,(⊂addr),ports,⍪pclts 24 | :EndIf 25 | r←State'' 26 | ⍝- Session already started as server 27 | ⍝- Argument must be 'ip-address' (ip-ports) 28 | ⍝- IP-address nor IP-ports can be empty 29 | ⍝- Already added: 30 | ⍝- Unable to connect to 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Dyalog Ltd. 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 | -------------------------------------------------------------------------------- /Source/ynys/expose.dyalog: -------------------------------------------------------------------------------- 1 | expose←{⍺←⊢ 2 | (src snm)←⍵ 3 | (tgt tnm)←⍺⊣# ⍝ dflt target - # 4 | tnm←snm∘⊣⍣(tgt≡tnm)⊢tnm ⍝ dflt tnames - snames 5 | ss←⍕src 6 | trap←'⋄0::(⊃⍬⍴⎕DMX.EN) ⎕SIGNAL ⎕DMX.⎕EN⋄' 7 | fix←{op←4=src.⎕NC ⍵ 8 | (aa ww)←2↑(0 2⊃src.⎕AT ⍵)⍴'⍺⍺' '⍵⍵' 9 | ⍝ tgt.⎕FX(⍺,'←{⍺←⊢')trap('⍵≡⍺⍵:',aa,ss,'.',⍵,ww,'⊢⍵')('⍺(',aa,ss,'.',⍵,ww,')⍵')'}' 10 | tgt.⎕FX,⊂⍺,'←{⍺←⊢',trap,(op/'⍵≡⍺⍵:',aa,ss,'.',⍵,ww,'⊢⍵⋄'),'⍺(',aa,ss,'.',⍵,ww,')⍵}' 11 | } 12 | z←tnm fix¨snm 13 | 1:1 14 | ⍝ expose public functions and operators elsewhere 15 | ⍝ ⍵ (src) (snms) 16 | ⍝ ⍺ [tgt [tnms] ] 17 | ⍝ src ref containing fns to run 18 | ⍝ snms names of fns & ops therein 19 | ⍝ tgt ref to contain fns to call - dflt # 20 | ⍝ tnms corresponding names in tgt - dflt snms 21 | ⍝ tgt.tnm←{⍺←⊢⋄0::(⊃⍬⍴⎕DM)⎕SIGNAL⎕EN⋄ ⍺( #.src.snm )⍵} ⍝ function 22 | ⍝ tgt.tnm←{⍺←⊢⋄0::(⊃⍬⍴⎕DM)⎕SIGNAL⎕EN⋄ ⍵≡⍺⍵:⍺⍺#.src.snm ⊢⍵⋄⍺(⍺⍺#.src.snm )⍵} ⍝ adverb 23 | ⍝ tgt.tnm←{⍺←⊢⋄0::(⊃⍬⍴⎕DM)⎕SIGNAL⎕EN⋄ ⍵≡⍺⍵:⍺⍺#.src.snm⍵⍵⊢⍵⋄⍺(⍺⍺#.src.snm⍵⍵)⍵} ⍝ conjunction 24 | } 25 | -------------------------------------------------------------------------------- /Source/ynys/RemoveServer.dyalog: -------------------------------------------------------------------------------- 1 | r←RemoveServer server;mask;iso;isos;clt;mask2;local;ok;count 2 | :If 2=⎕NC'session.procs' 3 | :If 0=⍴server ⋄ server←whoami'' ⋄ :EndIf 4 | :If ∨/mask←server∘≡¨session.procs[;2] 5 | :If 2=⎕NC'session.assoc.proc' 6 | :If 0≠≢isos←(mask2←session.assoc.proc∊mask/session.procs[;0])/session.assoc.iso 7 | :For iso :In isos 8 | {}DRC.Close'Iso',⍕iso 9 | :EndFor 10 | session.assoc.(busy iso proc)/⍨←⊂~mask2 11 | :EndIf 12 | :EndIf 13 | 14 | :For clt :In mask/session.procs[;4] 15 | {}DRC.Close clt 16 | :EndFor 17 | 18 | :If 0≠≢local←{(⍵≠0)/⍵}mask/session.procs[;1] 19 | :If 'server'≡options.status 20 | local.Kill 21 | :Else 22 | count←0 23 | :While ~ok←∧/local.HasExited 24 | ⎕DL session.retry_interval×count←count+1 25 | :Until count>session.retry_limit 26 | :EndIf 27 | :EndIf 28 | session.procs⌿⍨←~mask 29 | r←State'' 30 | :Else 31 | r←'[server "',server,'" not found]' 32 | :EndIf 33 | :Else 34 | r←'[no servers defined]' 35 | :EndIf 36 | -------------------------------------------------------------------------------- /Source/ynys/isolates.dyalog: -------------------------------------------------------------------------------- 1 | isolates←{⍺←⊢ 2 | ss←session 3 | source←⍵ ⍝ ns or wsid 4 | receive←'#.isolate.ynys.receive' ⍝ abs ns path known in remote ws 5 | numid←ss.nextid←ss.nextid+2 6 | tgt←'#.',chrid←'Iso',⍕numid 7 | 8 | ⍝ ss.procs contains all proc ids; assoc.(busy/proc) only those with busy isos 9 | max←options.isolates 10 | ass←ss.assoc 11 | z←⎕TGET ss.assockey ⍝ see Init 12 | procs←⊣/ss.procs 13 | load←¯1+{≢⍵}⌸procs,ass.proc ⍝ isolate load 14 | busy←¯1+{≢⍵}⌸procs,ass.(busy/proc) ⍝ busy isolates 15 | ok←options.isolates>load 16 | ~∨/ok:'ISOLATE ERROR: All processes are in use'⎕SIGNAL 11⊣⎕TPUT ss.assockey 17 | use←(load=⌊/load)/⍳⍴load ⍝ procs with same load 18 | proc←procs⊃⍨use←use[⊃⍋busy[use]] ⍝ pick one with fewest busy isolates 19 | ass.(iso busy proc),←numid 0,proc 20 | z←⎕TPUT ss.assockey 21 | (host port)←ss.procs[procs⍳proc;2 3] 22 | data←host ss.orig chrid numid tgt ss.homeport port 23 | id←dix'host orig chrid numid tgt home port'data 24 | z←connect chrid host port,⊂receive(source options.listen id) 25 | id 26 | ⍝ Create DRC client for isolate. 27 | ⍝ Create id space to send and return for corresponding proxy. 28 | } 29 | -------------------------------------------------------------------------------- /Source/ynys/Reset.dyalog: -------------------------------------------------------------------------------- 1 | r←Reset kill;iso;clt;ok;local;count;z;close 2 | r←'' 3 | close←{0::'' ⋄ DRC.Close ⍵} ⍝ try to close, ignore all errors 4 | 5 | :If 2=⎕NC'session.assoc.iso' 6 | r,←(⍕≢session.assoc.iso),' isolates, ' 7 | :For iso :In session.assoc.iso ⍝ For each known isolate 8 | {}close'Iso',⍕iso 9 | :EndFor 10 | :EndIf 11 | 12 | :If 2=⎕NC'session.procs' 13 | r,←(⍕≢session.procs),' processes, ' 14 | :For clt :In session.procs[;4] ⍝ For each process 15 | {}close clt 16 | :EndFor 17 | 18 | count←0 19 | :If 0≠≢local←session.((procs[;1]≠0)/procs[;1]) ⍝ local processes 20 | 21 | :While ~ok←∧/local.HasExited 22 | ⎕DL session.retry_interval×count←count+1 23 | :Until count>session.retry_limit 24 | 25 | :If ~ok 26 | :If 'server'≡options.status 27 | r←r,' (service processes killed), ' ⋄ z←local.Kill 28 | :Else 29 | r←r,' (service processes have not died), ' 30 | :EndIf 31 | :EndIf 32 | :EndIf 33 | 34 | :EndIf 35 | 36 | :If 2=⎕NC'session.listeningtid' 37 | ⎕TKILL session.listeningtid 38 | r,←'callback listener, ' 39 | :EndIf 40 | 41 | :If 0≠≢r ⋄ r←'Reset: ',¯2↓r 42 | :Else ⋄ r←'Nothing found to reset' 43 | :EndIf 44 | ⎕EX'session' 45 | -------------------------------------------------------------------------------- /Source/ynys/localServer.dyalog: -------------------------------------------------------------------------------- 1 | r←localServer r;srv;rc;z;old 2 | →(0=⎕NC'session.homeport')⍴0 3 | 4 | :If r=0 5 | :AndIf r←DRC.Exists srv←'ISO',⍕session.homeport ⍝ Server exists 6 | {}DRC.Close srv ⍝ Left over - object there but no thread 7 | :If 2=⎕NC'session.listeningtid' 8 | ⎕TKILL session.listeningtid 9 | ⎕EX'session.listeningtid' 10 | :EndIf 11 | :Else 12 | 13 | :If r←DRC.Exists srv←'ISO',⍕session.homeport ⍝ Server exists 14 | :If r←2=⎕NC'session.listeningtid' 15 | :AndIf r←session.listeningtid∊⎕TNUMS 16 | :Else 17 | {}DRC.Close srv ⍝ Left over - object there but no thread 18 | :EndIf 19 | :EndIf 20 | 21 | :If ~r ⍝ Already got a listening server 22 | :AndIf options.listen 23 | old←{0::0 ⋄ 2503⌶⍵}3 ⍝ Make thread and children un-interruptible 24 | :Repeat 25 | :If r←0=rc←⊃z←1 1 ##.RPCServer.Run srv session.homeport 26 | session.listeningtid←1⊃z 27 | :ElseIf 10048=rc ⍝ Socket already in use 28 | session.homeport+←options.(1+processes×processors) 29 | :EndIf 30 | :Until r∨session.homeport>options.homeportmax 31 | old←{0::0 ⋄ 2503⌶⍵}old ⍝ Restore thread state 32 | ('Unable to create listener: ',,⍕z)⎕SIGNAL r↓11 33 | :EndIf 34 | :EndIf 35 | -------------------------------------------------------------------------------- /Source/ynys/decode.dyalog: -------------------------------------------------------------------------------- 1 | res←where decode(a b c d e);home;x;DMX 2 | home←where=# ⍝ would be #.IsoNNNNN for outward call 3 | x←where.⍎ 4 | :Trap 999×{0::0 ⋄ ##.onerror≡⍵}'debug' 5 | :Select a 6 | :Case 0 ⋄ res←0(x b) 7 | :Case 1 ⋄ res←0((x b)c) 8 | :Case 2 ⋄ res←0(b(x c)d) 9 | :Case 3 ⍝ Assignment 10 | :If (0=⍴⍴c)∧1=≡c ⋄ where.⎕FX c ⋄ res←0 c ⍝ c is ⎕OR 11 | :Else ⋄ res←0(c⊢b{x ⍺,'←⍵'}c) 12 | :EndIf 13 | :Case 4 ⋄ res←0(⍎'c⌷[d]where.',b) 14 | :Case 5 ⋄ res←0(⍎'(c⌷[d]where.',b,')←e') 15 | :EndSelect 16 | :Else 17 | :If ⎕DMX.(EN ENX)≡11 4 ⍝ DOMAIN ERROR: isolate function iSyntax does not exist ... 18 | res←11((⊂'ISOLATE ERROR: Callbacks not enabled'),1↓⎕DM) 19 | :ElseIf ⎕DMX.((EN=6)∧∨/'##'⍷,⍕DM) 20 | res←6((⊂'VALUE ERROR IN CALLBACK'),1↓⎕DM) 21 | :Else 22 | res←⎕DMX.(EN DM) 23 | :EndIf 24 | :EndTrap 25 | ⍝ ⍺ target space 26 | ⍝ ⍵ encoded list 27 | ⍝ decode list and execute requisite syntax in target. 28 | ⍝ return (0 value) if OK, (⎕EN ⎕DM) on failure 29 | ⍝ Syntax cases: 30 | ⍝ a | b | c | d | e 31 | ⍝ 0 | array | | | 32 | ⍝ 0 | nilad | | | 33 | ⍝ 0 | (expr) | | | 34 | ⍝ 1 | monad | rarg | | 35 | ⍝ 2 | larg | dyad | rarg | 36 | ⍝ 3 | array | value | | 37 | ⍝ 4 | array | indices | axes | 38 | ⍝ 5 | array | indices | axes | value 39 | -------------------------------------------------------------------------------- /Source/ynys/getSet.dyalog: -------------------------------------------------------------------------------- 1 | getSet←{⍺←⊢ 2 | 3 | 0∊⍴⍵:{(~⍵[;0]∊'debug' 'status')⌿⍵}options.({⍵,⍪⍎⍕⍵}⎕NL-2 9) 4 | one←1=≡⍵ 5 | two←one<(,2)≡⍴⍵ 6 | sig11←⎕SIGNAL∘11 7 | msg←'Argument should be '''', name or (name value)' 8 | one⍱two:sig11 msg 9 | (nam new)←⊂⍣one⊢⍵ 10 | ''≢0⍴nam:sig11 msg 11 | nam←minuscule nam 12 | ~(⊂nam)∊options.⎕NL-2 9:sig11'Unknown parameter: ',nam 13 | old←options.⍎nam 14 | one:old 15 | 16 | ⍝ then two 17 | and←{⍺⍺⊣⍵:⍵⍵⊣⍵ ⋄ 0} 18 | (range type)←(domains types).⍎⊂nam 19 | (s b i r a)←'SBIRA'=type 20 | msg←nam,' should be a',⍕s b i r/'string' 'boolean' 'integer' 'ref' 21 | 22 | ok←a ⍝ Any array 23 | ok←ok∨b and(⊢≡1=⊢)new 24 | ok←ok∨i and{0=1↑0⍴⍵}and(⊢=⌊)new 25 | ok←ok∨s and{''≡0⍴⍵}new 26 | ok←ok∨r and{9=⎕NC'⍵'}new 27 | ~ok:sig11 msg 28 | ok←ok∧((1=⍴)∨(⊂new)∊⊢)range 29 | ~ok:sig11⍕nam,' should be one of:',range 30 | 31 | (ws db li)←'workspace' 'debug' 'listen'∊⊂nam ⍝ special 32 | 0::(⊃⎕DMX.DM)⎕SIGNAL ⎕DMX.EN ⍝ 33 | z←checkWs⍣(ws∧new≢'')⊢new ⍝ 34 | z←{⎕THIS.(trapErr←(⍵↓0)∘⊣) ⋄ 0}⍣db⊢new ⍝ 35 | z←localServer⍣li⊢new ⍝ cases 36 | old⊣nam options.{⍎⍺,'←⍵'}new 37 | 38 | ⍝ }⍵ 39 | ⍝ ⍺ target space 40 | ⍝ ⍵ '' | name | name value 41 | ⍝ ← (⍵:'') all names and values 42 | ⍝ (⍵:name) value 43 | ⍝ (⍵:name value) value re-assigned 44 | ⍝ called by both Config and setDefaults 45 | } 46 | -------------------------------------------------------------------------------- /Source/ynys/InitProcesses.dyalog: -------------------------------------------------------------------------------- 1 | r←ss InitProcesses op;z;count;limit;ok;maxws;ws;rt;iso;ports;pids;pclts;procs;m;wd;of;ri 2 | (count limit)←0 3 3 | maxws←' MAXWS=',⍕op.maxws 4 | ws←op.workspace 5 | (ri of wd)←op.(rideinit outfile workdir) 6 | :If (⊂rt←op.runtime)∊0 1 ⍝ if rt is boolean 7 | rt←rt∧op.onerror≢'debug' ⍝ force runtime←0 if onerror≡'debug' 8 | :EndIf 9 | iso←('isolate=isolate onerror=',(⍕op.onerror),' isoid=',(⍕ss.callback),maxws) 10 | iso,←' protocol=',op.protocol,' quiet=1' 11 | iso,←' ',op.cmdargs 12 | :If ws∨.≠' ' ⋄ ws←1⌽'""',checkWs addWSpath ws ⋄ :EndIf ⍝ if no path ('\/') 13 | ports←ss.homeport+1+⍳op.(processors×processes) 14 | 15 | :Repeat 16 | :If 0∊m←checkPortIsFree¨ports 17 | ⎕←'*** Warning - isolate port(s) in use: ',(~m)/ports 18 | ports←ports+1+⍴ports 19 | :EndIf 20 | :Until (∧/m)∨(⊃ports)>1+op.homeportmax 21 | 'ISOLATE: Unable to find free ports'⎕SIGNAL(∧/m)↓11 22 | 23 | pids←(1⊃⎕AI)+⍳⍴ports 24 | 25 | :Repeat 26 | count+←1 27 | procs←{⎕NEW ##.APLProcess(ws ⍵ rt ri of wd)}∘{'AutoShut=1 Port=',(⍕⍵),' APLCORENAME=',(⍕⍵),' ',iso}¨ports 28 | procs.onExit←{'{}#.DRC.Close ''PROC',⍵,''''}¨⍕¨pids ⍝ signal soft shutdown to process 29 | 30 | pclts←pids InitConnections ss.orig ports ss.callback ss.remoteclients 31 | 32 | :If ~ok←~∨/0∊≢¨pclts ⍝ at least one failed 33 | ⎕←'ISOLATE: Unable to connect to started processes (attempt ',(⍕count),' of ',(⍕limit),')' 34 | ⎕DL 5 ⋄ {}procs.Kill ⋄ ⎕DL 5 35 | ports+←1+op.(processors×processes) 36 | :EndIf 37 | :Until ok∨count≥limit 38 | 'ISOLATE: Unable to initialise isolate processes'⎕SIGNAL ok↓11 39 | r←pids,procs,(⊂ss.orig),ports,⍪pclts 40 | -------------------------------------------------------------------------------- /Source/ynys/execute.dyalog: -------------------------------------------------------------------------------- 1 | r←execute(name data);z;n;space;zz;wsid;⎕TRAP 2 | :If name≡'' 3 | :Select data 4 | :Case 'identify' ⍝ return isoid 5 | r←0(⊃1⊃⎕VFI+##.RPCServer.GetEnv'isoid') 6 | :Else 7 | r←11('ISOLATE: Unknown command'data) 8 | :EndSelect 9 | :Else 10 | :Trap 6 ⋄ space←#.⍎name 11 | :Else ⍝ Seems the isolate ns was not created 12 | r←6('Isolate initialization failed - check workspace name' '' '^') 13 | →0 14 | :EndTrap 15 | 16 | :Hold 'ISO_',name 17 | :If {0::0 ⋄ ##.onerror≡⍵}'debug' 18 | wsid←⎕WSID 19 | ⎕TRAP←0 'E' '⎕WSID←''ISOLATE - '',{(100⌊⍴⍵)↑⍵},⍕2↑⎕DM ⋄ ⎕←↑⎕DM ⋄ ⎕←''To resume:'',(⎕UCS 13),'' →⎕LC''' 20 | r←space decode 5↑data 21 | ⎕WSID←wsid ⋄ z←{0::0 ⋄ 2022⌶⍵}0 ⍝ Flush session caption 22 | :Else 23 | r←space decode 5↑data 24 | :EndIf 25 | 26 | :If 0=⎕NC'session' ⍝ In the isolate 27 | :Trap 6 28 | z←+r ⍝ Block on futures here to provoke (and trap) the VALUE ERROR 29 | :Else 30 | :If 2=⎕NC n←name,'error' 31 | r←⍎n ⋄ ⎕EX n 32 | (1 0⊃r),←' IN CALLBACK' 33 | :Else 34 | r←6('VALUE ERROR: Callback failed'(1⊃data)'^') 35 | :EndIf 36 | :EndTrap 37 | :If (⎕NC⊂'zz'⊣zz←1⊃r)∊9.2 9.4 9.5 38 | r←11('ISOLATE ERROR: Result cannot be returned from isolate' '') 39 | :EndIf 40 | 41 | :EndIf 42 | r←cleanDM r 43 | :EndHold 44 | :EndIf 45 | ⍝ this is the function called by RPCServer.Process 46 | ⍝ ⍵ name data 47 | ⍝ data list created by encode below. 48 | ⍝ ← result or assignment of decoded ⍵ 49 | -------------------------------------------------------------------------------- /Source/ynys/Init.dyalog: -------------------------------------------------------------------------------- 1 | {r}←{allowremote}Init local;here;z;ss;op;maxws;ws;rt;iso;ports;pids;pclts;t 2 | r←⎕THIS.⎕IO←0 3 | :If 0=⎕NC'allowremote' ⋄ allowremote←⍬ ⋄ :EndIf 4 | :If newSession'' 5 | here.iSpace←here←⎕THIS 6 | z←here.(proxyClone←⎕NS'').⎕FX¨proxySpace.(⎕CR¨↓⎕NL 3) 7 | z←here.proxyClone.⎕FX iSpace.⎕CR'tracelog' 8 | ss←here.session←⎕NS'' 9 | here.(signal←⎕SIGNAL/∘{(⊃⍬⍴⎕DM)⎕EN}) 10 | z←setDefaults'' 11 | op←options 12 | z←getSet'debug'op.debug ⍝ on or off 13 | :Trap trapErr'' 14 | ##.DRC←here.DRC←getDRC op.drc 15 | :If 9.2≠⎕NC⊂'DRC' ⍝ if DRC is not an instance of Conga.LIB 16 | :AndIf ~(⊃z←DRC.Init ⍬)∊0 17 | ('CONGA INIT FAILED: ',,⍕z)⎕SIGNAL 11 18 | :EndIf 19 | z←DRC.SetProp'.' 'Protocol'(op.protocol) 20 | ss.retry_limit←99 ⍝ How many retries 21 | ss.retry_interval←0.05 ⍝ Length of first wait (increases with interval each wait) 22 | ss.orig←whoami'' 23 | ss.homeport←op.homeport 24 | ss.listen←localServer options.listen ⍝ ⌽⊖'ISOL' 25 | ss.nextid←2⊃⎕AI ⍝ isolate id 26 | ss.callback←1+(2*15)|+/⎕AI ⍝ queue for calls back 27 | ss.remoteclients←allowremote 28 | z←⎕TPUT ss.assockey←1+ss.callback ⍝ queue for assoc and procs 29 | ss.assoc←dix'proc iso busy'(3⍴⍬ ⍬) 30 | ss.procs←0 5⍴0 ⍬'' 0 '' 31 | 32 | :If 1≡local ⍝ if we're to start local processes 33 | ss.procs⍪←ss InitProcesses op 34 | :EndIf 35 | 36 | ss.started←sessionStart'' ⍝ last thing so we know 37 | 38 | :If ss.listen ⍝ set up list of acceptable client addresses 39 | t←↑{0=⊃p←DRC.GetProp ⍵'peeraddr':2↑1↓1⊃p ⋄ ⍬ ⍬}¨session.procs[;4] 40 | t[;0]←{⌽(1+⍵⍳':')↓⍵}∘⌽¨t[;0] 41 | ##.RPCServer.localaddrs←(⊂''),⊣⌸t 42 | :EndIf 43 | r←1 44 | :Else 45 | signal'' 46 | :EndTrap 47 | :EndIf 48 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # isolate.dws and samples for Dyalog v17.0 2 | 3 | Since version 14.0, Dyalog APL has included the workspace `isolate.dws`, which enables simple asynchronous and parallel programming in Dyalog APL. From version 17.0, the source for the isolate workspace and associated samples have been moved to GitHub. 4 | 5 | ## Documentation 6 | 7 | Documentation for the futures and isolates can be found in the [Dyalog Documentation Centre](http://docs.dyalog.com/16.0/Parallel%20Language%20Features.pdf). 8 | 9 | ## Version 17.0 Enhancements 10 | 11 | A project was started at Dyalog with the goal of enhancing the isolate workspace so that it would be possible to run *isolate servers* in the cloud. In the end, it turned out that a couple of bugfixes in the code which validated peer IP addresses to deal with IPv6 addresses was all that was really required. 12 | 13 | We also added a "sample" called `AWS`, which provides an interface to the Amazon Webservices Command Line Interface. This can be used to launch and manage AWS instances. [Dyalog Webinar 10](https://dyalog.tv/Webinar/?v=bpP99KEfUxI) demonstrates how to use this to run a large number of parallel isolates on the cloud. 14 | 15 | ## Samples 16 | 17 | In addition to the workspace which can be found in the `ws` folder along with most other distributed workspaces, a new folder of isolate-related samples are now installed in the folder `Samples/isolate` below the main Dyalog folder. 18 | 19 | It contains the AWS class and an example of how to use it, and also includes the `IIPageStats` sample, which computes letter frequencies used on all major newspaper sites in a given state in the USA, as an example of how to use the `ll.EachX` (extended parallel each) tool: 20 | 21 | ### Contents of the Samples folder 22 | 23 | |File|Type|Description| 24 | |----|----|-----------| 25 | |AWS.dyalog|Class|Interface to the Amazon Webservices Command Line Interface| 26 | |AWSIsolates.dyalog|Function|Shows how to use the AWS class to start a set of virtual machines and use them to run Isolates| 27 | |IIPageStats.dyalog|Namespace|Demonstrates the use of `ll.EachX` tool| 28 | 29 | 30 | -------------------------------------------------------------------------------- /Tests/test_syntax.dyalog: -------------------------------------------------------------------------------- 1 | z←test_syntax dummy;fail;ns;is 2 | ⍝ Check all syntax cases 3 | ⍝ (see isolate.ynys.decode function) 4 | ⍝ a | b | c | d | e 5 | ⍝ 0 | array | | | 6 | ⍝ 0 | nilad | | | 7 | ⍝ 0 | (expr) | | | 8 | ⍝ 1 | monad | rarg | | 9 | ⍝ 2 | larg | dyad | rarg | 10 | ⍝ 3 | array | value | | 11 | ⍝ 4 | array | indices | axes | 12 | ⍝ 5 | array | indices | axes | value 13 | 14 | {}#.isolate.Config'listen' 0 15 | {}#.isolate.Config'processors' 4 16 | 17 | {}#.isolate.Reset 0 18 | 19 | ⍝ Create test isolate 20 | fail←'r←1÷~fail' 21 | ns←⎕NS'' 22 | ns.fail←0 23 | ns.mat←3 4⍴⍳12 24 | ns.⎕FX'r←nil'fail'r←42' 25 | ns.⎕FX'r←mon x'fail'r←42 x' 26 | ns.⎕FX'r←x dya y'fail'r←42 x y' 27 | is←#.ø ns 28 | 29 | ⍝ Test working cases 30 | is.fail←0 31 | 'is.mat'Fail ns.mat Check is.mat ⍝ Case 0 32 | 'is.nil'Fail ns.nil Check is.nil 33 | 'is.(21+21)'Fail 42 Check is.(21+21) 34 | 'is.mon'Fail(ns.mon 0)Check is.mon 0 ⍝ Case 1 35 | 'is.dya'Fail(0 ns.dya 0)Check 0 is.dya 0 ⍝ Case 2 36 | is.newmat←2 2⍴⍳4 ⍝ Case 3 37 | 'is.newmat'Fail is.newmat Check 2 2⍴⍳4 38 | 'is.mat[1;]'Fail ns.mat[1;]Check is.mat[1;] ⍝ Case 4 39 | ns.mat[3;]←⍳4⊣is.mat[3;]←⍳4 ⍝ Case 5 40 | 'is.mat (ii)'Fail ns.mat Check is.mat 41 | 42 | :Trap 2 43 | ns.mat[3;]←is.mat[3;]←⍳4 ⍝ /// This fails 44 | :Else 45 | Log'Still not fixed: http://mantis.dyalog.com/view.php?id=11096' 46 | :EndTrap 47 | 48 | ⍝ Now test failing cases 49 | 50 | is.fail←1 ⍝ Should make all the defined fns crash 51 | 6 'VALUE ERROR' 'nosuchvar'expect'is.nosuchvar' ⍝ Case 0 52 | 11 'DOMAIN ERROR' 'nil[1] r←1÷~fail'expect'is.nil' 53 | 11 'DOMAIN ERROR' '(1÷0)'expect'is.(1÷0)' 54 | 11 'DOMAIN ERROR' 'mon[1] r←1÷~fail'expect'is.mon 0' ⍝ Case 1 55 | 11 'DOMAIN ERROR' 'dya[1] r←1÷~fail'expect'0 is.dya 0' ⍝ Case 2 56 | ⍝ is.(2+2)←3 ⍝ Can't think of a way to get Case 3 to fail in isolate 57 | 3 'INDEX ERROR' 'mat[...]'expect'+is.mat[4;]' ⍝ Case 4 58 | 3 'INDEX ERROR' 'mat[...]←...'expect'+is.mat[4;]←2 2⍴3 4' ⍝ Case 5 59 | 60 | z←'' 61 | -------------------------------------------------------------------------------- /Source/ynys/setDefaults.dyalog: -------------------------------------------------------------------------------- 1 | setDefaults←{⍺←⊢ 2 | here←⎕THIS 3 | new←0=here.⎕NC⊂'options' ⍝ set defaults only once 4 | z←{ 5 | spaces←here.(types options domains)←here.⎕NS¨⍬ ⍬ ⍬ 6 | tod←{(2⍴⍵),⊂1↓⍵} ⍝ type: Str Bool Int Ref 7 | ⍝ ensure all param names are minuscule as arg to Config is converted thus. 8 | ⍝ spaces.param← tod 'S' 'Default' 'and' 'the' 'Rest' 9 | spaces.debug←tod'B' 0 ⍝ cut back on error 10 | spaces.drc←tod'R'# ⍝ copy into # if # and missing 11 | spaces.listen←tod'B' 0 ⍝ can isolate call back to ws 12 | spaces.onerror←tod'S' 'signal' 'debug' 'return' 13 | spaces.processors←tod'I'(processors ⍬) ⍝ no. processors (fn ignores ⍵) 14 | spaces.processes←tod'I' 1 ⍝ per processor 15 | spaces.isolates←tod'I' 99 ⍝ per process 16 | spaces.homeport←tod'I' 7051 ⍝ first port to attempt to use 17 | spaces.homeportmax←tod'I' 7151 ⍝ highest port allowed 18 | spaces.runtime←tod'A' 1 ⍝ use runtime version 19 | spaces.protocol←tod'S' 'IPv4' 'IPv6' 'IP' ⍝ default to IPv4 20 | spaces.maxws←tod'S'(##.RPCServer.GetEnv'MAXWS') 21 | spaces.status←tod'S' 'client' 'server' ⍝ set as 'server' by StartServer 22 | spaces.workspace←tod'S'(getDefaultWS'isolate.dws') ⍝ load current ws for remotes? 23 | spaces.rideinit←tod'S' '' ⍝ RIDE_INIT for APLProcess 24 | spaces.outfile←tod'S' '' ⍝ log file prefix for APLProcess 25 | spaces.workdir←tod'S' '' ⍝ working directory for APLProcess 26 | spaces.cmdargs←tod'S' 'ENABLE_CEF=0' ⍝ add to command line 27 | 1:1 28 | }⍣new⊢0 29 | 0::(⊃⍬⍴⎕DMX.DM)⎕SIGNAL ⎕DMX.EN 30 | ⊢getSet ⍵ ⍝ this where Config called prior Init 31 | ⍝ called by Config before Init runs and by Init when it does. 32 | ⍝ set default options and permit user changes 33 | ⍝ but leave Init to apply them. 34 | } 35 | -------------------------------------------------------------------------------- /Samples/IIPageStats.dyalog: -------------------------------------------------------------------------------- 1 | :Namespace IIPageStats 2 | ⍝ Future/Isolate code sample, using #.ll.EachX 3 | ⍝ Report 'al' 4 | ⍝ ... to get a letter frequency count for home pages of newspapers in Alabama 5 | 6 | (⎕IO ⎕ML)←1 7 | alphabet←'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' 8 | 9 | ∇ freq←{nprocs}Report state;pages;html;cap;PF;AI3;iss;HttpCommand 10 | AI3←⎕AI[3] 11 | 12 | :If 0=⎕NC'nprocs' ⋄ nprocs←#.isolate.Config'processors' ⋄ :EndIf ⍝ Default to use all processors 13 | iss←#.ø¨nprocs⍴⎕THIS ⍝ Make isolates 14 | 15 | ⎕SE.SALT.Load 'HttpCommand' 16 | 17 | {}iss.{0⊣⎕FIX ⍵}⊂⎕SRC HttpCommand ⍝ Transfer HttpCommand to isolates 18 | pages←PapersInState state 19 | cap←'Processing ',(⍕≢pages),' major papers in state "',state,'"' 20 | freq←('CountPageChars' ''cap #.ll.EachX iss)pages 21 | freq←⊃+/freq 22 | freq←(26↑alphabet),⍪+⌿2 26⍴freq 23 | freq←freq[⍒freq[;2];] 24 | 25 | ⎕←'Elapsed seconds for ',state,': ',1⍕⎕AI[3]-AI3 26 | ∇ 27 | 28 | ∇ pages←PapersInState state;text;ignore;txt 29 | ⍝ Retrieve list of home pages of major newspapers in named state 30 | ⍝ Thanks to USNPL.com - the US NewsPaper List 31 | 32 | text←GetPage'http://www.usnpl.com/',state,'news.php' 33 | 34 | ⍝ ↓↓↓ extract the body containing newpaper page links 35 | txt←(('for address downloads.'⍷text)⍳1)↓text 36 | txt←(5+(''⍷txt)⍳1)↓txt 37 | txt←(¯1+(''⍷txt)⍳1)↑txt 38 | 39 | pages←('()'⎕S'\2.com/')txt ⍝ All href's to a .com 40 | ∇ 41 | 42 | ∇ r←CountPageChars url;text;html 43 | ⍝ Return letter frequency count for a URL 44 | 45 | html←{0::'' ⋄ GetPage ⍵}url 46 | text←('<.*?>'⎕R'')html ⍝ Remove all (well, lots of) HTML tags 47 | text←(text∊alphabet)/text ⍝ Remove all irrelevant chars 48 | r←¯1+{≢⍵}⌸alphabet,text ⍝ Frequency count 49 | ∇ 50 | 51 | ∇ r←GetPage url;headers;rc;z 52 | ⍝ Get an HTTP page - throw any errors using ⎕SIGNAL 53 | 54 | z←HttpCommand.Get url 55 | :If 0=z.rc 56 | r←z.Data 57 | :Else 58 | (⍕z.(HttpStatus HttpMessage))⎕SIGNAL 11 59 | :EndIf 60 | ∇ 61 | 62 | :EndNamespace 63 | -------------------------------------------------------------------------------- /OriginalNotes/configuration.txt: -------------------------------------------------------------------------------- 1 | Configuration options 2 | (from the setDefaults private function: 3 | ,------------------------------------------------------------------------------, 4 | | options.debug←0 ⍝ cut back on error | 5 | | options.drc←# ⍝ copy into # if # and missing | 6 | | options.workspace←'isolate' ⍝ load current ws for remotes? | 7 | | options.listen←0 ⍝ can isolate call back to ws | 8 | | options.processors←processors 4 ⍝ no. processors | 9 | | options.processes←1 ⍝ per processor | 10 | | options.runtime←'R'∊3⊃'.'⎕WG'APLVersion' ⍝ use runtime version unless devt| 11 | | options.status←'client' ⍝ set as 'server' by StartServer | 12 | '------------------------------------------------------------------------------' 13 | debug: 14 | All public fns and ops set an error guard as: 15 | trapErr''::signal'' 16 | If options.debug is 0 (the default) then trapErr'' returns 0 so all errors are trapped and signalled back to the session. 17 | If 1 then trapErr'' is ⍬, there is no trap and errors are signalled at the point of error. 18 | 19 | drc: 20 | If drc is # (the default) we check to see if #.DRC exists and copy if not. Otherwise drc must be a ref to the extant DRC namespace. 21 | 22 | workspace: 23 | This is the workspace to be ⎕LOADed by APLProcess (default "isolate") that must contain the isolate namespace and call 'isolate.ynys.isoStart ⍬' in its ⎕LX. It should be in one of the folders defined in [Options]-[Configure...]-[Workspace] in the session menubar or be specified with a full path. 24 | 25 | listen: 26 | Whether the isolates will be enabled to call back to the active ws to request further data &c. (default 0.) 27 | When enabled an "instance" of RPCServer is created locally to receive requests from the isolates. 28 | Such requests are issued to the "parent" of the remote isolate accessed as ##. which is enabled as an isolate in its own right. 29 | 30 | processors: 31 | The number of prosessors in the machine. Currently available from Windows but default 4 elsewhere. 32 | 33 | processes: 34 | The number of processes that will be started per prosessor in the machine. (default 1.) 35 | 36 | runtime: 37 | Whether the runtime equivalent of the active interpreter should be started for the slave processes. If the active interpreter (the default) is runtime then runtime anyway. 38 | 39 | status: 40 | Set by StartServer and AddServer. 41 | 42 | -------------------------------------------------------------------------------- /Samples/AWSIsolates.dyalog: -------------------------------------------------------------------------------- 1 | AWSIsolates n;iss;procs;z;data;start;cmd;iAWS;wait;p;instances 2 | ⍝ Start n instances of an Ubuntu 16.04 server running Dyalog 16.0 and use them as isolate servers. 3 | ⍝ For more information and to see it in action, see the Webinar "APL Processes and Isolates in the cloud", 4 | ⍝ https://dyalog.tv/Webinar/?v=bpP99KEfUxI 5 | 6 | start←⎕AI[3] 7 | wait←1 ⍝ constant: do not change this value 8 | 9 | :If 16<2⊃⎕VFI 4↑2⊃'.'⎕WG'APLVersion' ⍝ /// To avoid crashing with v17.0 arrays sent down sockets 10 | 'v16.0 or earlier is required for this demo; AWS images are running v16.0'⎕SIGNAL 11 11 | :EndIf 12 | 13 | iAWS←⎕NEW AWS 14 | 15 | ⍝ ↓↓↓ Modify these settings to suit your own situation 16 | iAWS.user←'ubuntu' ⍝ User name to log in to 17 | iAWS.keyfolder←'C:\Users\mkrom\Documents\SSH\' ⍝ Where the key files are 18 | iAWS.keypair←'AWS-JSONServer' ⍝ Name of an AWS key pair 19 | iAWS.region←'eu-west-1' ⍝ Ireland 20 | iAWS.image←'ami-ce366ab7' ⍝ Ubuntu 16.04 with Dyalog APL 16.0 and updated isolate workspace 21 | iAWS.type←'t1.micro' ⍝ instance-type 22 | iAWS.security←'Isolate ssh RIDE' ⍝ Ports to open 23 | ⎕←iAWS.(↑{⍵(⍎⍵)}¨⎕NL-2) ⍝ Display all public fields 24 | 25 | ⎕←'Starting ',(⍕n),' instances of Amazon Machine Image ',iAWS.image,' in region ',iAWS.region 26 | ⎕←' Current IP is: ',iAWS.SetMyIp 27 | 28 | ⎕←instances←wait iAWS.RunInstances n 29 | 30 | ⎕←'' ⋄ ⎕←'Starting Isolate Servers...' 31 | ⎕←' ',cmd←'isolate=isolate Port=7052 AutoShut=1 AllowRemote="IP=',iAWS.myIP,'" dyalog /home/ubuntu/isolate' 32 | procs←iAWS.Launch n cmd 33 | 34 | ⎕←'' ⋄ ⎕←'Connecting to Isolate Servers:' 35 | :For p :In procs 36 | z←#.isolate.AddServer p.Address 7052 37 | :EndFor 38 | 39 | ⎕←'' ⋄ ⎕←'Create isolates and execute uname -a in each:' 40 | iss←isolate.New¨n⍴⊂'' 41 | ⎕←⍪iss.⎕SH⊂'uname -a' 42 | 43 | z←⎕AI[3] 44 | ⎕←'' ⋄ ⎕←'Using isolates to averate each row of a ',(⍕n),' by 10,000 matrix' 45 | data←?n 10000⍴0 ⍝ create "big data" 46 | ⎕←'Transferring data...' 47 | iss.data←↓data 48 | ⎕←'Computing averages in parallel' 49 | ⎕←3⍕iss.((+⌿÷≢)data) 50 | ⎕←'Total runtime for parallel computation: ',(⍕⎕AI[3]-z),' ms including data transfer' 51 | 52 | ⎕EX'iss' ⍝ dispose of isolates 53 | 54 | ⎕←'' ⋄ ⎕←'Shutting down isolate servers' 55 | ⍝ Isolate servers were started with AutoShut=1; when we remove the master socket they will shut down 56 | z←#.isolate.RemoveServer¨procs.Address ⋄ {}⎕TSYNC procs.TID 57 | 58 | ⎕←procs[1].Output ⍝ SSH session output from one of the processes 59 | 60 | EXIT: 61 | ⎕←'' ⋄ ⎕←'Terminating AWS instances' 62 | wait iAWS.Terminate instances[;1] 63 | 64 | ⎕←'' ⋄ ⎕←'Test complete, total elapsed time = ',(1⍕0.001×⎕AI[3]-start),' seconds' 65 | -------------------------------------------------------------------------------- /Build/Build.dyalog: -------------------------------------------------------------------------------- 1 | Build;version;db;root;warn;ver;vn;glyph;name;nr;date;dir;getEnv 2 | ⍝ As part of running isolate.dbuild, tweak the workspace a bit: 3 | ⍝ Build cover-functions in #.isolate 4 | ⍝ Insert isolate.Version to include GIT last commit date 5 | getEnv←{2 ⎕NQ'.' 'GetEnvironment'⍵} 6 | version←'1.6' ⍝ base version - update this whenever there is a version bump 7 | db←1⊃⎕RSI ⍝ Ref to DyalogBuild environment 8 | root←db.path 9 | warn←'' 10 | #.isolate.⎕EX'APLProcess' ⍝ make sure any APLProcess loaded from isolate is expunged 11 | :If 0∊⍴dir←getEnv'MK_LIBRARY_CORE' 12 | :OrIf ~⎕NEXISTS dir 13 | dir←'[DYALOG]/Library/Core' 14 | :EndIf 15 | ⎕SE.SALT.Load dir,'/APLProcess -target=#.isolate' ⍝ load the "official" APLProcess 16 | ⍝ check if APLProcess.Version>2.2.7 and signal error if not... 17 | ver←2⊃#.isolate.APLProcess.Version 18 | vn←2⊃'.'⎕VFI ver 19 | :If ¯1=vn(×2⊥×⍤-)2 2 8 ⍝ minimum version should be 2.2.8 20 | ('APLProcess.Version should be 2.2.8 or greater, it is currently: ',ver)⎕SIGNAL 11 21 | :EndIf 22 | ⍝ Build cover functions with typeable names in #.isolate 23 | :For glyph name :In ('II' 'll')('IIÐ' 'llKey')('IIö' 'llRank')('IÏ' 'llEach')('o_II' 'llOuter') 24 | :If 0∊⍴nr←#.⎕NR glyph 25 | 11 ⎕SIGNAL⍨'isolate Build: Could not find #.',glyph 26 | :EndIf 27 | nr[1]←⊂name,'←'((¯1∘+⍳⍨)↓⊢)1⊃nr 28 | :If 0=1↑0⍴#.isolate.⎕FX nr 29 | 11 ⎕SIGNAL⍨'Unable to define cover function #.isolate.',name 30 | :EndIf 31 | :EndFor 32 | :Trap 0 33 | :If 0≠⍴date←⍕{0::'' ⋄ ⊃⎕CMD'git -C "',⍵,'" log -1 --format=%ci'}root 34 | :OrIf 0≠⍴date←⍕{0::'' ⋄ ⊃⎕CMD'cd "',⍵,'" && svn info | sed -n "s/^Last Changed Date: \\(.*\\) (.*/\\1/p"'}root 35 | date←' (',date,')' 36 | :Else 37 | ⍝ MBaas: signalling an error means build-failure -> only do that when we build for production, but be tolerant when we build for tests 38 | :If 2=db.⎕NC'prod' 39 | :AndIf db.prod 40 | :AndIf 0<≢2 ⎕NQ #'GetEnvironment' 'NODATENEEDED' ⍝ NODATENEEDED indicates that the production build does not need to have a date (we use this when running tests) 41 | 'isolate Build: Unable to get GIT last commit date - isolate. Version not set!'⎕SIGNAL 11 42 | :Else 43 | warn←'isolate Build: Unable to get GIT last commit date ! ' 44 | date←'' 45 | :EndIf 46 | :EndIf 47 | :Else 48 | warn←'isolate Build: trapped error getting last commit date:',(⎕JSON ⎕OPT'Compact' 0)⎕DMX 49 | date←'' 50 | :EndTrap 51 | ver←version,date ⍝ Join base version and git last commit date 52 | isolate.Version←'Version ',ver,' built at ',,'ZI4,<->,ZI2,<->,ZI2,< >,ZI2,<:>,ZI2,<:>,ZI2'⎕FMT 1 6⍴⎕TS 53 | :If ~0∊⍴warn ⋄ db.Log warn ⋄ :EndIf 54 | db.Log'isolate.Version set to: ',isolate.Version 55 | -------------------------------------------------------------------------------- /Tests/test_basic.dyalog: -------------------------------------------------------------------------------- 1 | z←test_basic dummy;time;delta;is;ns;test;dfns;double;rtack;n2 2 | ⍝ Take futures and isolates for a little spin 3 | 4 | rtack←⍎⎕UCS 8866 ⍝ // work aroung bug in Log 5 | {}#.isolate.Config'listen' 0 6 | {}#.isolate.Config'processors' 4 7 | 8 | {}#.isolate.Reset 0 9 | 10 | test←'Basic IÏ test' 11 | double←{⍵+⍵}#.IÏ⍳4 12 | 13 | ⎕DL 0.5 14 | :If 2 4 6 8≢double 15 | :AndIf 2 4 6 8≡⊃¨double 16 | Log'*** WARNING: Still not fixed: http://mantis.dyalog.com/view.php?id=15672' 17 | :EndIf 18 | 19 | double←⊃¨double 20 | test Fail 2 4 6 8 Check double ⍝ Remove the above :If clause when the bug is fixed 21 | 22 | test←'Basic II test' 23 | 24 | :Trap 0 ⍝ https://github.com/Dyalog/isolate/issues/17 25 | test Fail 4 Check 2+#.II 2 26 | :Else 27 | Log'*** WARNING: Still not fixed: https://mantis.dyalog.com/view.php?id=20113' 28 | :EndTrap 29 | 30 | test Fail(,'4')Check⍕2+#.II 2 ⍝ https://github.com/Dyalog/isolate/issues/17 31 | 32 | time←3⊃⎕AI ⋄ z←⎕DL #.IÏ 4⍴1 33 | :If 100delta←(3⊃⎕AI)-time 38 | 39 | time←3⊃⎕AI ⋄ z←⎕DL #.II 1 ⍝ https://github.com/Dyalog/isolate/issues/16 40 | :If 100delta←(3⊃⎕AI)-time 45 | 46 | ⍝ Check that passing argument to defined functions does not block... 47 | time←3⊃⎕AI ⋄ z←{⍵ ⍵}⎕DL #.IÏ 1 48 | :If 100delta←(3⊃⎕AI)-time 53 | 54 | n2←(2×1111⌶⍬) 55 | :If (2 2⍴2)≢1+#.IÏ 2 2⍴1 56 | :OrIf (2 2⍴2)≢(2 2⍴1)+#.IÏ 1 57 | 'Dyadic +IÏ on 2x2 matrix returns wrong result'Fail 1 58 | :EndIf 59 | 60 | :Trap 0 ⍝ https://github.com/Dyalog/isolate/issues/15 61 | z←+/⎕DL #.IÏ 2 2⍴1 62 | :Else 63 | 'IÏ on 2x2 matrix fails'Fail 1 64 | :EndTrap 65 | 66 | 67 | :Trap 0 68 | z←⎕NC #.ll.Each n2⍴⊂⊂'...' 69 | 'll.Each'Fail(n2⍴¯1)Check z 70 | :Else 71 | ((⊃⎕DM),' in ll.Each: ')Fail 1 72 | :EndTrap 73 | 74 | ⍝ Check isolate creation options 75 | #.data←42 ⍝ NB must not be localised 76 | #.dup←{⍵ ⍵} 77 | 78 | ns←#.⎕NS'dup' 'data' 79 | is←#.ø ns ⍝ Clone namespace 80 | 'Clone isolate from NS'Fail(3 2)Check is.⎕NC↑'dup' 'data' 81 | 82 | is←#.ø'dup' 'data' ⍝ Create is from name list 83 | 'Create isolate from namelist'Fail(3 2)Check is.⎕NC↑'dup' 'data' 84 | #.⎕EX'dup' 'data' 85 | 86 | 'Unable to find dfns.dws'Fail 0=≢dfns←FindWs'dfns.dws' 87 | is←#.ø dfns 88 | 'Create isolate from dfns.dws'Fail(,3)Check is.⎕NC'queens' 89 | 90 | 91 | z←'' 92 | -------------------------------------------------------------------------------- /Source/ynys/proxySpace.dyalog: -------------------------------------------------------------------------------- 1 | :Namespace proxySpace 2 | (⎕IO ⎕ML ⎕WX)←0 1 3 3 | 4 | iEvaluate←{z←{0::0 ⋄ 2503⌶⍵}3 ⍝ Thread and its children are un-interruptible 5 | ⍺←⊢ 6 | data←⍺ iSpace.encode ⍵ 7 | ID←iD.numid 8 | ss←{iSpace.session}⍣home⊢home←2∊⎕NC'iSpace.session.started' ⍝ is this true ? 9 | z←{iso←ss.assoc.iso 10 | (≢iso)≤i←iso⍳⍵:'ISOLATE: No longer accessible'⎕SIGNAL 6 11 | (i⊃ss.assoc.busy)←1}⍣home⊢ID 12 | (rc res)←z←iSend iD.tgt data ⍝ the biz 13 | ok←0=rc 14 | ~home:{rc=0:⍵ ⋄ ⍎'#.Iso',(⍕ID),'error←rc ⍵' ⋄ ⎕SIGNAL rc}res ⍝ call back? then we're done 15 | z←ss.assoc.{((iso⍳⍵)⊃busy)←0}ID 16 | ok:⊢res ⍝ spiffing! 17 | (,⍕(⍕rc),': ',(0⊃res),{(⍵∨.≠' ')/': ',⍵}1⊃res,'' '')iSpace.qsignal rc 18 | ⍝ execute expression supplied to isolate 19 | } 20 | 21 | ∇ r←iSend data;send;ev;nm;rc;res;cmd 22 | send←'#.isolate.ynys.execute' data ⍝ RPCServer runs this 23 | :Trap 0 ⋄ res←iSpace.DRC.Send iD.chrid send 24 | :Else 25 | 'ISOLATE: Transmission failure'iSpace.qsignal 6 26 | :EndTrap 27 | rc cmd ev data←4↑res 28 | :If 0≠rc ⋄ r←86(('COMMUNICATIONS FAILURE ',⍕rc cmd)ev) ⍝ ret ⎕EN ⎕DM 29 | :Else 30 | WAIT: 31 | :Trap 1000 32 | :Repeat 33 | res←(rc nm ev data)←4↑iSpace.DRC.Wait cmd 34 | :Until ~(rc=100)∨(⊂ev)∊'Progress' 'Timeout' ⍝ Tolerate eventmode on (ev="Timeout") or off (rc=100) 35 | :Else 36 | iSpace.checkLocalServer ⍬ 37 | ⍞←'ISOLATE: Interrupt - continue waiting (Y/N)? ' 38 | →(∨/'Yy'∊⊃{(1+⍵⍳'?')↓⍵}⍞~' ')⍴WAIT 39 | ('USER INTERRUPT ',⍕⎕DMX.EN)iSpace.qsignal 6 40 | :EndTrap 41 | :If rc=0 42 | :If 0=⊃data ⋄ r←1⊃data ⍝ if rc is 0 res which will be (0 result) 43 | :Else ⋄ r←(1↑data),⊂1↓data,⊂'' ⍝ error from RPCServer framework itself 44 | :EndIf 45 | :Else ⋄ r←rc((⍕rc nm)ev) ⍝ else return rc and faked ⎕DM 46 | :EndIf 47 | ⍝ called from iSyntax, iEvaluate and from 48 | ⍝ ##.cleanup to remove isolate from remote process 49 | :EndIf 50 | ∇ 51 | 52 | iSyntax←{⍺←⊢ 53 | ⍝z←tracelog ⍵ 54 | c←⊣/⍵ 55 | '('=c:⊢3 32 ⍝ if '(expr)' ⍝ 1 0 0 0 0 0 56 | '{'=c:⊢3 52 ⍝ if '{defn}' ⍝ 1 1 0 1 0 0 57 | '#'∊⍵:⊢0 0 ⍝ # in anything un-parenthesised is an error 58 | '⎕'=c:⊢{0::0 0 ⋄ x←⍎⍵ ⋄ c←⎕NC'x' ⋄ (2 3⍳c)⊃(2 0)(3 52)(0 0)}⍵ ⍝ assumes ⎕FNS ambi 59 | ⍝ ↑ reject ops & nss 60 | f←',⊢-⊂⍴⊃≡+!=⍳⊣↓↑|⍪⍕⍎∊⌽~×≠>⌊∨?⌷<≢⌈≥⍷⍉∪÷⍒⊥∧⍋⊖*○⍲⍱⍟⌹⊤≤∩' 61 | 62 | c∊f:⊢3 52 63 | 0>⎕NC ⍵:⊢0 0 ⍝ primitive operators 64 | expr←'((2⍴⎕nc∘⊂,⎕at),''',⍵,''')' ⍝ then what is it? 65 | (rc res)←iSend iD.tgt(0 expr) ⍝ from the horse's mouth 66 | rc≠0:(,⍕res)iSpace.qsignal rc ⍝ lost connection? 67 | (nc at)←res ⍝ ⎕NC ⎕AT - rc? 68 | nc∊3.2 3.3:⊢3 52 ⍝ 3,32+16+4 res ambi omega 69 | c←⌊nc ⍝ class 70 | c∊0 2:⊢2 0 ⍝ undef, var 71 | (r fv ov)←at ⍝ result, valence 72 | w←∨/(a d w)←fv=¯2 2 1 ⍝ (ambi, dyad, omega) 73 | r←c,2⊥r a d w 0 0 ⍝ class, encoded syntax 74 | 1:⊢r 75 | ⍝ return nameclass and syntax for supplied name (string) 76 | } 77 | 78 | :EndNamespace 79 | -------------------------------------------------------------------------------- /Source/root/ll.dyalog: -------------------------------------------------------------------------------- 1 | :Namespace ll 2 | ⍝ Parallel Extensions 3 | 4 | (⎕IO ⎕ML ⎕WX)←1 1 3 5 | 6 | Each←{⍺←⊢ ⋄ ⍺ (⍺⍺ EachX ⍬) ⍵} 7 | 8 | ∇ r←{left}(fns EachX iss)right;dyadic;fn;cb;n;counts;shape;ni;i;count;done;failed;next;callbk;expr;z;PF;cblarg;cancelled;cbprovided;noIso;cr 9 | ⍝ IÏ using queueing on persistent Isolates: 10 | ⍝ 11 | ⍝ iss is a list of refs to pre-existing isolates to use 12 | ⍝ or if scalar, processors×processes clones will be made 13 | ⍝ 14 | ⍝ fns is either: 15 | ⍝ a simple char vec name of function expected to be in supplied isolates 16 | ⍝ a nested vec of two fn names, in which case 2nd name is a progress callback 17 | ⍝ use empty callback fn for default display 18 | 19 | :If dyadic←2=⎕NC'left' ⍝ Scalar extension 20 | :If 1=×/⍴left ⋄ left←(⍴right)⍴left 21 | :ElseIf 1=×/⍴right ⋄ right←(⍴left)⍴right 22 | :EndIf 23 | :EndIf 24 | 25 | :If noIso←0=≢iss ⋄ iss←⊂'' ⋄ :EndIf ⍝ No isolate passed 26 | 27 | :If 3=⎕NC'fns' ⍝ A real function? 28 | :If 1=⍴⍴cr←⎕CR'fns' ⋄ fns←cr''⊣⎕EX'fns' ⋄ ⍝ primitive? 29 | :Else 30 | :If noIso ⋄ iss←⎕NS'' ⋄ :EndIf 31 | fn←⊃(,iss).⎕FX⊂⎕CR'fns' 32 | fns←fn'' ''⊣⎕EX'fns' 33 | :EndIf 34 | 35 | :EndIf 36 | 37 | :If 0=⍴⍴iss ⍝ If scalar, clone 38 | iss←#.isolate.{New¨(×/Config¨'processors' 'processes')⍴⍵}iss 39 | :EndIf 40 | 41 | :If cbprovided←2=≡fns ⍝ We MAY have a callback function 42 | (fn cb cblarg)←3↑fns,'' '' 43 | cbprovided←cb∨.≠' ' ⍝ We DO have a callback function 44 | cblarg,←(0=≢cblarg)/'ll.Each Progress - ',(⍕fn),' (',(⍕×/⍴right),')' 45 | :Else ⍝ No callback function defined 46 | fn←fns ⋄ (cb cblarg)←'' 'll.Each Progress' 47 | :EndIf 48 | 49 | :If 0=⍴cb ⍝ Default Progress Form 50 | :If PEACHForm cblarg(≢iss)(×/⍴right) 51 | cb←'PEACHUpdate' 52 | :EndIf 53 | :EndIf ⍝ Default 54 | 55 | :If cbprovided 56 | callbk←(⊃⎕RSI)⍎cb 57 | :Else 58 | callbk←⍎cb,(0=≢cb)/'{0}' 59 | :EndIf 60 | 61 | ni←≢iss 62 | shape←⍴right 63 | n←⍴right←,right ⋄ :If dyadic ⋄ left←,left ⋄ :EndIf 64 | counts←ni⍴0 ⋄ done←failed←n⍴count←0 65 | r←n⍴⎕NULL 66 | 67 | expr←(dyadic/'(next⊃left) '),'iso.{',(dyadic/'⍺ '),'(',(⍕fn),') ⍵} next⊃right' 68 | cancelled←0 69 | :If 1=≢iss ⍝ Only one: do it in main thread 70 | z←1 run1iso⊃iss 71 | :Else 72 | z←⎕TSYNC(⍳ni)run1iso&¨iss 73 | :EndIf 74 | ⎕SIGNAL cancelled/6 75 | ∇ 76 | 77 | ∇ r←PEACHForm(caption nprocs nitems);p;labels;pos;pb;n 78 | ⍝ Make a progress form with a progress bar per process and one for the total 79 | 80 | :Trap 0 81 | r←1⊣'PF'⎕WC'Form'caption('Coord' 'Pixel')('Size'((40+25×nprocs)800))('Border' 3) 82 | :Else ⋄ →r←0 ⍝ Unable to create a form 83 | :EndTrap 84 | PF.texts←PF.bars←(1+nprocs)⍴PF 85 | labels←({'Iso #',⍕⍵}¨⍳nprocs),⊂'Total' 86 | 87 | :For p :In ⍳1+nprocs 88 | pos←10+25×p-1 89 | ('PF.L',⍕p)⎕WC'Label'(p⊃labels)(pos 20)(⍬ 60) 90 | (n←'PF.T',⍕p)⎕WC'Label' '0'(pos 70)(⍬ 30)('justify' 'right') 91 | PF.texts[p]←⍎n 92 | (n←'PF.PB',⍕p)⎕WC'ProgressBar'((pos+3)110)(⍬ 655)('Limits'(0 nitems)) 93 | PF.bars[p]←⍎n 94 | :EndFor 95 | 2 ⎕NQ'.' 'Flush' 96 | ∇ 97 | 98 | ∇ {abort}←cap PEACHUpdate arg 99 | :Trap abort←0 100 | PF.texts.Caption←⍕¨arg 101 | PF.bars.Thumb←arg 102 | :Else 103 | abort←1 ⍝ User killed the GUI 104 | :EndTrap 105 | ∇ 106 | 107 | ∇ z←isoix run1iso iso;next 108 | ⍝ drive isolate #iso until we are done 109 | ⍝ NB semi-globals from EachX: r n count counts bclarg callbk cancelled 110 | 111 | z←0 112 | :While n≥next←count←count+1 ⍝ no more to do 113 | r[next]←⊂⍎expr 114 | counts[isoix]+←1 115 | z←{0::failed[⍵]←1 ⋄ done[⍵]←1⊣+r[⍵]}next ⍝ Reference it 116 | :If cblarg callbk counts,count⌊n 117 | →0⊣z←''⊣cancelled←1 118 | :EndIf 119 | :EndWhile 120 | ∇ 121 | 122 | :EndNamespace 123 | -------------------------------------------------------------------------------- /Samples/AWS.dyalog: -------------------------------------------------------------------------------- 1 | :Class AWS 2 | 3 | ⍝ Tool for launching AWS images 4 | 5 | ⍝ Assumes AWS Command Line interface is installed and configured 6 | ⍝ See https://docs.aws.amazon.com/cli/latest/userguide/installing.html 7 | 8 | :Field Public user ← '' ⍝ User name to log in to 9 | :Field Public keyfolder ← '' ⍝ Where the key files are 10 | :Field Public keypair ← '' ⍝ Name of an AWS key pair 11 | :Field Public region ← '' ⍝ e.g. eu-west-1 for Ireland 12 | :Field Public image ← '' ⍝ e.g. ami-4e612f37ami-xxxxxxx 13 | :Field Public type ← '' ⍝ instance-type, e.g. t1.micro 14 | :Field Public security ← '' ⍝ Security profiles to attach 15 | 16 | :Field Public myIP ← '' ⍝ current IP address (if SetMyIp has been called) 17 | :Field Public AWSver ← '' ⍝ AWS-CLI version info 18 | 19 | ∇ make;z 20 | ⍝ Create an instance and verify that AWS-CLI is available 21 | 22 | :Access Public 23 | :Implements Constructor 24 | 25 | :If ∨/'aws-cli'⍷z←∊⎕SH'aws --version 2>&1' 26 | AWSver←z 27 | :Else 28 | 'Amazon Web Service Command Line Interface does not seem to be available'⎕SIGNAL 12 29 | :EndIf 30 | ∇ 31 | 32 | ∇ {r}←SetMyIp;z 33 | ⍝ Set my public IP address using https://api.ipify.org 34 | ⍝ NB will load #.HttpCommand if not present 35 | 36 | :Access Public 37 | 38 | r←'' 39 | :Trap 0 40 | :If 0=⎕NC '#.HttpCommand' 41 | ⎕SE.SALT.Load 'HttpCommand -target=#' 42 | :EndIf 43 | z←#.HttpCommand.Get 'https://api.ipify.org?format=json' 44 | :If 0=z.rc 45 | :AndIf 200=z.HttpStatus 46 | r←myIP←(⎕JSON z.Data).ip 47 | :EndIf 48 | :EndTrap 49 | 50 | ∇ 51 | 52 | ∇ r←CurrentState img;ns;filters 53 | ⍝ Return matrix of instance-id, image-id, state, public-ip 54 | ⍝ Right argument can be '' to filter using default image, * to not filter, or a specific image id 55 | 56 | :Access Public 57 | 58 | ⍝ Decide whether to filter on an image 59 | :If (,img)≡,'*' ⋄ filters←'' 60 | :Else ⋄ filters←' --filters Name=image-id,Values=',img,(0=≢img)/image 61 | :EndIf 62 | 63 | ns←JSONcmd'aws ec2 describe-instances --region ',region,filters,' --output json' 64 | r←StateInfo ns.Reservations.Instances 65 | ∇ 66 | 67 | ∇ r←{wait}Terminate img;state;running;filter;ns;instances;done;n;cmd 68 | ⍝ Return matrix of instance-id, imake-id, state, public-ip 69 | ⍝ Right argument can be : '' to filter using default image, 70 | ⍝ : '*' to terminate all images 71 | ⍝ : simple char vec to terminate all instances of a specific image-id 72 | ⍝ : vec-of-vecs to terminate specific instance-ids 73 | 74 | :Access Public 75 | 76 | :If 0=⎕NC'wait' ⋄ wait←0 ⋄ :EndIf 77 | 78 | :If 2=≢img ⍝ vector of specific instance-ids 79 | state←CurrentState '*' 80 | state←(state[;1]∊img)⌿state 81 | :Else 82 | img←img,(0=≢img)/image ⍝ '' = default image 83 | state←CurrentState image 84 | :EndIf 85 | 86 | :If 0≠≢running←⍸state[;3]∊⊂'running' 87 | filter←' --instance-ids ',⍕state[running;1] 88 | 89 | ns←JSONcmd'aws ec2 terminate-instances --region ',region,filter,' --output json' 90 | r←↑ns.TerminatingInstances.(InstanceId''PreviousState.Name'') 91 | 92 | n←≢instances←r[;1] 93 | →wait↓0 94 | 95 | :Repeat 96 | r←(r[;1]∊instances)⌿r 97 | :If ~done←n≤+/r[;3]∊'terminated' 'shutting-down' 98 | ⎕←(,'ZI2,<:>,ZI2,<:>,ZI2,< >'⎕FMT 1 3⍴3↑3↓⎕TS),,⍕{⍺,≢⍵}⌸r[;3] 99 | ⎕DL 3 100 | r←CurrentState'' 101 | :EndIf 102 | :Until done 103 | :EndIf 104 | ∇ 105 | 106 | ∇ r←{wait}RunInstances n;cmd;z;done;instances;addr 107 | ⍝ Start n instances of the current image 108 | 109 | :Access Public 110 | 111 | :If 0=⎕NC'wait' ⋄ wait←0 ⋄ :EndIf 112 | 113 | cmd←'aws ec2 run-instances --region ',region,' --image-id ',image 114 | cmd,←' --count ',(⍕n),' --instance-type ',type 115 | cmd,←' --key-name ',keypair 116 | cmd,←' --security-groups ',security,' --output=json' 117 | ⎕←cmd 118 | r←JSONcmd cmd 119 | 120 | r←StateInfo,⊂r.Instances 121 | instances←r[;1] 122 | →wait↓0 123 | 124 | :Repeat 125 | r←(r[;1]∊instances)⌿r 126 | :If ~done←n≤+/r[;3]∊⊂'running' 127 | ⎕←(,'ZI2,<.>,ZI2,<.>,ZI2,< >'⎕FMT 1 3⍴3↑3↓⎕TS),,⍕{⍺,≢⍵}⌸r[;3] 128 | ⎕DL 3 129 | r←CurrentState'' 130 | :EndIf 131 | :Until done 132 | ∇ 133 | 134 | ∇ r←StateInfo is 135 | ⍝ Extract interesting State Information from an AWS "Instances" structure 136 | 137 | r←↑⊃,/is.(InstanceId ImageId State.Name({0::'' ⋄ PublicIpAddress}⍬)) 138 | ∇ 139 | 140 | 141 | ∇ r←Launch(n cmd);p;running;i;z;cmd;solo;host;state 142 | ⍝ Launch n processes using available instances 143 | 144 | :Access Public 145 | 146 | state←CurrentState image 147 | r←⍬ 148 | 149 | ⍝ Check we have enough running instances 150 | :If n>≢running←⍸state[;3]∊⊂'running' 151 | p←≢⍸state[;3]∊⊂'pending' 152 | ('Only ',(⍕≢running),' running instances',(p≠0)/'(',(⍕p),' pending)')⎕SIGNAL 11 153 | :EndIf 154 | 155 | solo←1 ⍝ Only allow one dyalog process on the machine 156 | 157 | :For i :In running 158 | host←⊃state[i;4] 159 | z←⎕NEW RemoteProcess(host user keypair cmd solo) 160 | r,←z 161 | :EndFor 162 | ∇ 163 | 164 | ∇ r←JSONcmd cmd;z 165 | ⍝ Run a shell command which is supposed to return JSON 166 | 167 | z←∊⎕SH cmd,' 2>&1' 168 | :Trap 0 169 | r←⎕JSON z 170 | :Else 171 | z ⎕SIGNAL 11 172 | :EndTrap 173 | ∇ 174 | 175 | :Class RemoteProcess 176 | 177 | :Field Public TID←¯1 178 | :Field Public uname←'' 179 | :Field Public Address←'' 180 | :Field Public Process←⎕NULL 181 | :Field Public Command←⎕NULL 182 | :Field Public Output←⎕NULL 183 | 184 | (CR LF)←⎕UCS 13 10 185 | QSH←{CR@(=∘LF)⎕UCS 2⊃⍺.Exec ⍵} ⍝ Unix Command, replacing LF by CR 186 | 187 | ∇ r←Running 188 | :Access Public 189 | r←TID∊⎕TNUMS 190 | ∇ 191 | 192 | ∇ Start(host user key cmd solo);host;sess;public;private;z;ok 193 | :Access Public 194 | :Implements Constructor 195 | 196 | :If 0=⎕NC'#.SSH' 197 | 'aplssh needs to be loaded into #'⎕SIGNAL 6 198 | :EndIf 199 | 200 | public←##.keyfolder,key,'.pub' 201 | private←##.keyfolder,key,'.pem' 202 | 203 | :Repeat 204 | :Trap 701 801 ⍝ Sometimes the first SSH attempt fails 205 | Process←⎕NEW #.SSH.Session(host 22) 206 | Command←⎕NEW #.SSH.Session(host 22) 207 | Process.Userauth_Publickey user public private'' 208 | Command.Userauth_Publickey user public private'' 209 | ok←1 210 | :Else 211 | ⎕←' Error ',⎕DMX.Message,' connecting to ',host,' - retrying' 212 | ⎕DL 2 213 | ok←0 214 | :EndTrap 215 | :Until ok 216 | 217 | :If solo 218 | :AndIf 0≠≢Dyalogs 219 | ∘∘∘ ⍝ already busy 220 | :EndIf 221 | 222 | z←{0::0 ⋄ 2503⌶⍵}2 ⍝ Children of this thread should be un-interruptible 223 | TID←RunProcess&cmd 224 | z←{0::0 ⋄ 2503⌶⍵}z ⍝ Restore thread interruption setting 225 | Address←host 226 | ∇ 227 | 228 | ∇ r←RunProcess cmd 229 | ⍝ Run process and collect output 230 | r←Output←Process QSH cmd 231 | ∇ 232 | 233 | ∇ r←Dyalogs 234 | :Access Public 235 | ⍝ return ps output for running Dyalog processes 236 | :Access Public 237 | 238 | r←Command QSH 'ps -ef|grep dyalog|grep -v grep' 239 | ∇ 240 | 241 | ∇ r←SH cmd 242 | ⍝ Run shell command on the "Command" SSH connection 243 | :Access Public 244 | r←Command QSH cmd 245 | ∇ 246 | 247 | :EndClass 248 | 249 | 250 | :EndClass 251 | -------------------------------------------------------------------------------- /Source/isolate/RPCServer.dyalog: -------------------------------------------------------------------------------- 1 | :Namespace RPCServer 2 | (⎕IO ⎕ML)←1 1 3 | SendProgress←0 4 | Protocol←'' ⍝ Set to IPv4 or IPv6 to lock in 5 | StartTime←⎕TS 6 | Commands←Errors←CPU←0 7 | ß←{} ⍝ stub "fake" function to allow stats reporting 8 | DEBUG←0 ⍝ Set to 1 to get diagnostic messages 9 | 10 | ∇ r←Log text 11 | →DEBUG↓0 12 | ⎕←text 13 | ∇ 14 | 15 | ∇ r←GetEnv name;args;z 16 | ⍝ Look for environment settings, allowing Command Line overrides 17 | ⍝ Added to support isolates as bound executables under Windows 18 | 19 | :If 1=⍴z←name{(((1+≢⍺)↑¨⍵)∊⊂⍺,'=')/⍵}2 ⎕NQ'.' 'GetCommandLineArgs' 20 | r←(1+⍴name)↓⊃z 21 | :Else 22 | r←2 ⎕NQ'.' 'GetEnvironment'name 23 | :EndIf 24 | ∇ 25 | 26 | ∇ r←{folder}Launch(params port);z;folder;ws 27 | ⍝ Launch RPC Server as an external process 28 | ⍝ Params should include -Load= 29 | ⍝ See Boot for additional parameters 30 | ⍝ RPCServer.dyalog and RPCServer.dws must exist in same folder as current ws 31 | ⍝ /// Currently Windows only /// 32 | 33 | :If 0=⎕NC'folder' ⋄ folder←{(1-⌊/(⌽⍵)⍳'/\')↓⍵}⎕WSID ⋄ :EndIf 34 | ws←'"',folder,'RPCServer.DWS"' 35 | params←params,' Port=',⍕port 36 | r←⎕NEW #.APLProcess(ws params) 37 | ∇ 38 | 39 | ∇ r←DNSLookup args;port;address;noport 40 | ⍝ r[;1] protocol, [;2] address txt, [;3] numeric address, [;4] port 41 | ⍝ /// Should really make its way into DRC 42 | 43 | :If noport←1=≡args ⋄ args←args 80 ⋄ :EndIf 44 | (address port)←args 45 | :If 1037=⊃r←##.DRC.GetProp'.' 'tcplookup'address port 46 | r←##.DRC.GetProp'.' 'lookup'address port ⍝ name changed around Conga 2.5.22481 47 | :EndIf 48 | (⍕r)⎕SIGNAL(0≠1⊃r)⍴11 49 | r←(-noport)↓⍤1↑2⊃r 50 | r[;2]←((-(⍳∘':'⌽)¨r[;2]))↓¨r[;2] 51 | ∇ 52 | 53 | ∇ Boot;name;port;certfile;keyfile;sslflags;num;secure;load;l;folder;z;autoshut;quiet;allowremote;commasep;localaddrs 54 | ⍝ Bootstrap an RPC-Server using the following command line parameters 55 | ⍝ -Port=nnnn 56 | ⍝ -Load=.dyalog files to load before start 57 | ⍝ -AllowRemote=filter1,filter2,filter3 58 | ⍝ SSL Options 59 | ⍝ -CertFile=CertFile 60 | ⍝ -KeyFile=KeyFile 61 | ⍝ -SSLFlags=flags 62 | ⍝ /// Could be extended with 63 | ⍝ -Config=name of a configuration file 64 | ⍝ -ClientAddr=limit to connections from given site 65 | 66 | folder←{(1-⌊/(⌽⍵)⍳'/\')↓⍵}⎕WSID 67 | num←{⊃2⊃⎕VFI ⍵} 68 | sslflags←32+64 ⍝ Accept without Validating, RequestClientCertificate 69 | 70 | name←'RPCSRV' 71 | port←num GetEnv'Port' 72 | autoshut←num GetEnv'AutoShut' ⍝ Shut down if 1st connection is lost 73 | quiet←num GetEnv'Quiet' ⍝ Suppress diagnostic session output 74 | allowremote←GetEnv 'AllowRemote' ⍝ Remote access 75 | allowremote←(0≠≢allowremote)/{1↓¨(⍵=',')⊂⍵}',',allowremote 76 | 77 | :If 9.2≠##.⎕NC⊂'DRC' ⍝ if DRC is not an instance of Conga.LIB 78 | z←##.DRC.Init'' 79 | :EndIf 80 | 81 | localaddrs←⊃⍪/{0::0 3⍴⊂⍬ ⋄ DNSLookup ⍵}¨'' 'localhost' ⍝ Find all local addresses 82 | :If 0=≢localaddrs ⍝ /// paranoia: the above SHOULD work 83 | localaddrs←1 3⍴'IPv4' '127.0.0.1'(127 0 0 1) 84 | :EndIf 85 | 86 | :If 0≠⍴load←GetEnv'Load' 87 | load←{1↓¨(','=⍵)⊂⍵}',',load 88 | :For l :In load 89 | ⎕SE.SALT.Load folder,l,' -target=#' 90 | :EndFor 91 | :EndIf 92 | 93 | :If secure←0≠⍴certfile←GetEnv'CertFile' 94 | keyfile←GetEnv'KeyFile' 95 | sslflags←num GetEnv'SSLFlags' 96 | z←1 quiet autoshut Run name port('CertFile'certfile)('KeyFile'keyfile)('SSLFlags'sslflags) 97 | :Else 98 | z←1 quiet autoshut Run name port 99 | :EndIf 100 | 101 | :If 0≠1⊃z ⋄ Log z ⋄ ⎕DL 10 ⋄ :EndIf ⍝ /// Pop up? Log? 102 | 103 | 'TIMER' ⎕WC 'Timer' 5000 ('Event' 'Timer' 1) 104 | :Repeat 105 | z←⎕DQ'TIMER' 106 | :Until ~##.DRC.Exists name 107 | 108 | ⎕OFF 109 | ∇ 110 | 111 | ∇ r←End x 112 | r←done←x ⍝ Will cause server to shut down 113 | ∇ 114 | 115 | ∇ r←AllowRemoteAccess filters;i 116 | ⍝ By default, RPCServer will only accept connections from the local machine 117 | ⍝ /// validation? 118 | 119 | r←filters 120 | 121 | :If 0=⍴allowremote←r 122 | Log 'Local access only' 123 | :Else 124 | Log 'Remote access allowed for ',,⍕filters 125 | :EndIf 126 | ∇ 127 | 128 | ∇ Process(obj data);r;c 129 | ⍝ Process a call. data[1] contains function name, data[2] an argument 130 | 131 | :If SendProgress 132 | {}##.DRC.Progress obj(' Thread ',(⍕⎕TID),' started to run: ',,⍕data) ⍝ Send progress report 133 | :EndIf 134 | 135 | :If (,1⊃data)≡,'ß' ⍝ stats collection? 136 | r←(0(StartTime Commands Errors CPU)) 137 | :Else 138 | :Trap 0 ⋄ c←⎕AI[3] ⋄ r←0((⍎1⊃data)(2⊃data)) ⋄ CPU+←⎕AI[3]-c ⋄ Commands+←1 139 | :Else ⋄ r←⎕EN ⎕DM ⋄ Errors+←1 140 | :EndTrap 141 | :EndIf 142 | 143 | :Trap 11 144 | {}##.DRC.Respond obj r 145 | :Else 146 | {}##.DRC.Respond obj(99999('Unable to return result: ',⎕DMX.Message)) 147 | :EndTrap 148 | ∇ 149 | 150 | ∇ r←{start}Run args;sink;done;data;event;obj;rc;wait;z;cmd;name;port;protocol;srvparams;msg;rt;quiet;autoshut;tid;addr;ok;i;filter;first;⎕TRAP 151 | ⍝ Run a Simple RPC Server 152 | 153 | (name port)←2↑args 154 | srvparams←2↓args 155 | 156 | :If 0=⎕NC'start' ⋄ start←1 ⋄ :EndIf ⍝ start may be (start quiet) 157 | rt←'R'∊'.'⎕WG'APLVersion' ⍝ Runtime or DLLRT 158 | (start quiet autoshut)←0 rt 0∨3↑start 159 | 160 | :If (⊂Protocol)∊'IPv4' 'IPv6' ⋄ ##.DRC.SetProp'.' 'Protocol'Protocol ⋄ :EndIf 161 | 162 | :If start 163 | :If 0≠1⊃r←##.DRC.Srv(name''port'Command'),srvparams ⋄ :Return ⋄ :EndIf ⍝ Exit if unable to start server 164 | :If ~rt ⋄ tid←0 quiet autoshut Run&name port 165 | ⍝ Above line may start handler on separate thread 166 | ⍝ /// Looks like a bug to Morten if your application is in a runtime 167 | ⍝ /// That should probably only be done if BOOTING in a runtime 168 | ⍪quiet↓⊂'Server ''',name,''', listening on port ',⍕port 169 | ⍪quiet↓⊂' Handler thread started: ',⍕tid 170 | r←0 tid 171 | :Return ⍝ New TID 172 | :EndIf 173 | :EndIf 174 | 175 | ⍝ Handle the server (maybe in a new thread) 176 | ⍪quiet↓⊂'Thread ',(⍕⎕TID),' is now handling server ''',name,'''.' 177 | done←0 ⍝ Done←1 in function "End" 178 | first←'' 179 | 180 | :While ~done 181 | :Trap 1002 1003 ⍝ trap weak and strong interrupts - on UNIX kill -2 and kill -3 182 | 183 | rc obj event data←4↑wait←##.DRC.Wait name 10000 ⍝ Time out now and again 184 | 185 | :Select rc 186 | :Case 0 187 | :Select event 188 | :Case 'Error' 189 | ⍪quiet↓⊂'Error ',(⍕data),' on ',obj 190 | :If ~done∨←(⊂obj)∊name first ⍝ Error on the listener or 1st connection? 191 | {}##.DRC.Close obj ⍝ Close connection in error 192 | :EndIf 193 | 194 | :If autoshut=1 195 | :AndIf 0=#.DRC.Exists first 196 | ⍪quiet↓⊂'First connection lost - AutoShut initiated' 197 | done←1 ⋄ autoshut←2 198 | :If 9=⎕NC 'TIMER' ⋄ ⎕NQ 'TIMER' 'Timer' ⋄ :EndIf 199 | :EndIf 200 | 201 | :Case 'Receive' 202 | :If 2≠⍴data ⍝ Command is expected to be (function name)(argument) 203 | {}##.DRC.Respond obj(99999 'Bad command format') ⋄ :Leave 204 | :EndIf 205 | 206 | :If 3≠⎕NC cmd←1⊃data ⍝ Command is expected to be a function in this ws 207 | {}##.DRC.Respond obj(99999('Illegal command: ',cmd)) ⋄ :Leave 208 | :EndIf 209 | 210 | Process&obj data ⍝ Handle each call in new thread 211 | 212 | :Case 'Connect' ⍝ Set 'KeepAlive' to 10 seconds so we discover IP disconnections 213 | first,←(0=⍴first)/obj ⍝ bond to parent 214 | {}##.DRC.SetProp obj'KeepAlive' 10000 10000 215 | addr←{(-(⌽⍵)⍳':')↓⍵}2 2⊃##.DRC.GetProp obj'PeerAddr' 216 | addr←addr~'[]' 217 | addr←(7×'::ffff:'≡7↑addr)↓addr ⍝ IPv4 wrapped in IPv6 218 | 219 | :If ~ok←(⊂addr)∊localaddrs[;2] ⍝ remote 220 | :For i :In ⍳≢allowremote 221 | :Select 3↑filter←i⊃allowremote 222 | :CaseList 'ip=' 'IP=' ⍝ ip address 223 | :If ok←ok∨addr{⍵≡(⍴⍵)↑⍺}3↓filter ⋄ :Leave ⋄ :EndIf 224 | :EndSelect 225 | :EndFor 226 | 227 | :AndIf ~ok ⍝ Still not OK 228 | Log 'Connection refused from: ',addr 229 | Log 'Filters: ' allowremote 230 | {}##.DRC.Close obj ⍝ 'bye 231 | :EndIf 232 | 233 | :Case 'Timeout' ⍝ Eventmode Timeout - see 100 below 234 | 235 | :Else ⍝ Unexpected result? should NEVER happen 236 | ⎕←'Unexpected result "',event,'" from object "',name,'" - RPC Server shutting down' ⋄ done←1 237 | 238 | :EndSelect 239 | 240 | :Case 100 ⍝ Time out - Insert code for housekeeping tasks here 241 | 242 | :Case 1010 ⍝ Object Not Found 243 | ⎕←'Server object ''',name,''' has been closed - RPC Server shutting down' ⋄ done←1 244 | 245 | :Else 246 | ⎕←'Error in RPC.Wait: ',⍕wait 247 | :EndSelect 248 | :Else ⍝ got an interrupt 249 | ⎕←((1002 1003⍳⎕EN)⊃'Weak' 'Strong' 'Unknown?'),' interrupt received, RPC Server shutting down' 250 | done←1 251 | :EndTrap 252 | :EndWhile 253 | :If autoshut≠2 ⋄ ⎕DL 1 ⋄ :EndIf ⍝ Give responses time to complete 254 | {}##.DRC.Close name 255 | ⍪quiet↓⊂'Server ',name,' terminated.' 256 | ∇ 257 | 258 | :EndNamespace 259 | -------------------------------------------------------------------------------- /OriginalNotes/isolate.txt: -------------------------------------------------------------------------------- 1 | Implementation notes 2 | 3 | All contained in a single ns-tree - #.isolate - but aim to be relocatable as: 4 | 5 | '#.isolate' #.myns.⎕CY 'isolate' 6 | 7 | The isolate proxy returned by New is a container space created by ⎕NS in the caller having had 1∘(700⌶) applied to it. 8 | It contains the special functions: "iSyntax" & "iEvaluate", additional function iSend and a number of namespaces. 9 | 10 | The structure of the ns-tree has the main public fns and ops in the top level space - isolate - while most of the rest is in sub-space - ynys - (Welsh, island, cognate with insular, isolate &c.) and beginning with "y" it's at least at the bottom of autocomplete leaving the fns and ops at the top. The public fns are generated at build time from list (..ynys.publicMethods'') to call their homonyms in ynys and some of them are replicated in the root with names to suggest the primitives and derivations they model. 11 | 12 | The first time in a session that any isolates are created a function -Init - is run that creates a number of sub-spaces in ynys that contain constant or variable arrays to be used by 13 | 14 | Data spaces: 15 | 16 | session - created and populated by Init, it contains all constants & variables except those in: 17 | options - created and populated by setDefaults, it contains all user options. Maintained by Config. 18 | 19 | Tables: 20 | 21 | session.procs 22 | stores process instances and contact details in four columns: 23 | proc-id; proc-inst(0); host-addr; port-no 24 | proc-id integer incremented from zero when a process is started or made available. 25 | proc-inst instance of APLProcess if local or 0 placeholder if remote 26 | host-addr IP-address of machine where is process 27 | port-no port-number on which process listening; incremented from first available multiple of 7051 28 | 29 | session.assoc 30 | actually another data space or dictionary. 31 | for each isolate: 32 | iso a unique numeric id 33 | proc as proc-id in session.procs 34 | busy flag whether proxy currently awaiting response from isolate. 35 | seq iso-id of first isolate in "group" created under IÏ (llEach). 36 | 37 | isolate-id integer incremented from "random" seed; also in iD namespace in proxy and sent to isolate; names remote space as: 'IsoNNNN' 38 | proc-id as session.procs 39 | 40 | 41 | Functions: 42 | -- 43 | StartServer r←StartServer ⍵ 44 | Run an isolate server on one machine to be used by one or more others. 45 | This must be the first use of the isolate namespace in the session. 46 | Output should be similar to: 47 | ,------------------------------------------------------------------------, 48 | | isolate.StartServer'' | 49 | |Server 'ISO7051', listening on port 7051 | 50 | | Handler thread started: 1 | 51 | | Thread 1 is now handing server 'ISO7051'. | 52 | | | 53 | | IP Address: 192.168.0.2 | 54 | | IP Ports: 7052 7053 7054 7055 | 55 | | | 56 | |Enter the following in another session, in one or more another machines:| 57 | | | 58 | | #.isolate.AddServer '192.168.0.2' (7052-⎕IO-⍳4) | 59 | '------------------------------------------------------------------------' 60 | 61 | AddServer r← AddServer ⍵ 62 | use isolate server started in another machine 63 | ⍵ address ports 64 | address ip-address of host where isolates will reside. 65 | ports ports listening for isolate creation and execution. 66 | The argument will be given as output from the session in the server machine when started with: 67 | isolate.StartServer'' 68 | Multiple servers can be started in different machines and used by one or vice versa depending on resources available. 69 | isolates can be used as usual with New, llEach &c. but expressions will be evaluated in the other machine. 70 | 71 | New r←New ⍵ 72 | models isolate primitive: ¤ 73 | ⍵ source 74 | source code ∧/∨ data to be copied to isolate. 75 | ref or namelist expected to be qualified relative to caller. 76 | caller space from which this fn was called. 77 | when this is primitive both source & caller will be a matter of course. 78 | ← proxy 79 | proxy visible component of isolate. 80 | anonymous space child of caller. 81 | contains copies of iSyntax & iEvaluate, and refs - iSource to source, iCaller to the caller, iSpace to this space, iCarus - an instance of the "suicide" class and iD - containing the isolate id, the DRC client id, the port and the remote process id. 82 | 83 | Config r← Config ⍵ 84 | query or set configuration options. 85 | ⍵ '' | name | name value 86 | name one of params defined in setDefaults 87 | value new value for param 88 | ← ⍵:'' : table of all names and values 89 | ⍵:name : value 90 | ⍵:name value : old value having set new in param 91 | Config should normally be run before any isolate processes are created to ensure non-default values are honoured. 92 | Once any isolates have been created all but "debug" have been used and will e ignored. 93 | e.g. 94 | ,----------------------, 95 | | isolate.Config''| 96 | | debug 0 | 97 | | drc # | 98 | | listen 0 | 99 | | processes 1 | 100 | | processors 4 | 101 | | runtime 0 | 102 | | status client | 103 | | workspace isolate | 104 | '----------------------' 105 | -- 106 | Operators: 107 | -- 108 | ll r←⍺ (⍺⍺ ll) ⍵ 109 | parallel - models ⍺ ⍺⍺∥ ⍵ 110 | ⍺ optional left argument to ⍺⍺ 111 | ⍺⍺ function to run in an ephemeral isolate. 112 | ⍵ right arg to ⍺⍺ 113 | ← future - result of ⍺⍺ execured in isolate. 114 | 115 | llEach r←⍺ (⍺⍺ llEach) ⍵ 116 | parallel each - models ⍺ ⍺⍺∥¨ ⍵ 117 | ⍺ optional array itemwise compatible with ⍵; it's items are in the left domain of ⍺⍺. 118 | ⍺⍺ function presumed to be ambivalent or dyadic if ⍺ is supplied or monadic if not. 119 | ⍵ array whose items are in the right domain of ⍺⍺. 120 | ← array of futures with shape ←→ ⍴⍺⊢¨⍵ or ⍴⍵ 121 | 122 | llOuter r←⍺ (⍺⍺ llOuter) ⍵ 123 | parallel outer product - models ⍺ ∘.(⍺⍺∥) ⍵ 124 | ⍺ array whose items are in the left domain of ⍺⍺. 125 | ⍺⍺ dyadic function applied between all possible pairs of items of ⍺ and ⍵ 126 | ⍵ array whose items are in the right domain of ⍺⍺. 127 | ← the result is an array of futures with shape ←→ (⍴⍺),⍴⍵ 128 | 129 | llKey r←⍺ (⍺⍺ llKey) ⍵ 130 | parallel key - models ⍺ (⍺⍺∥⌸⍵⍵) ⍵ 131 | ⍺ optional array ; if present has same first dimension as ⍵ and its major cells are possibly repeated keys. 132 | if missing recall with: ⍵ ∇ ⍳≢⍵ 133 | ⍺⍺ ambivalent function to be applied for each unique key between a single key and the cells of ⍵ corresponding to all incidences of the key in ⍺. 134 | ⍵ array in the right domain of ⍺⍺ 135 | ← array of futures with shape as the number of unique keys each item being a single result of ⍺⍺. 136 | To emulate key (⌸) completely it should mix (↑) the results. 137 | This CANNOT BE DONE here as it dereferences the futures. 138 | 139 | llRank r←⍺ (⍺⍺ llRank ⍵⍵) ⍵ 140 | parallel rank - models ⍺ (⍺⍺∥⍤⍵⍵) ⍵ 141 | ⍺ optional array framewise compatible with ⍵; it's cells as defined by ⍵⍵ are in the left domain of ⍺⍺. 142 | ⍺⍺ function presumed to be ambivalent or dyadic if ⍺ is supplied or monadic if not. 143 | ⍵⍵ integer scalar or 1, 2 or 3 item vector defining the ranks of the cells to or between which function ⍺⍺ will be applied. 144 | ⍵ array whose cells as defined by ⍵⍵ are in the right domain of ⍺⍺ 145 | ← array of futures with shape determined by combination of ⍵⍵ and the sapes of ⍺ amd/or ⍵ 146 | ⍺⍺ is applied between corresponding cells of ⍺ and ⍵ or to the cells of ⍵. 147 | To emulate rank (⍤) completely it should mix (↑) the results. 148 | This CANNOT BE DONE here as it dereferences the futures. 149 | ----------------------------------------------------------------- 150 | Internals 151 | 152 | iSyntax - copied to proxy 153 | return name class and syntax code of supplied name 154 | 155 | ⍵ simple name, word or "()" or "{}" delimited expression, adjascent to the dot in iso.whatever 156 | ← class, syntax 157 | class ⎕NC of name in isolate. 158 | "(...)" - 3 159 | "{...}" - 3 160 | "⎕..." - 2 or 3 according {} 161 | syntax 2⊥1= result ambivalent dyadic rarg 0 0 162 | -------------------------------------------------------------------------- 163 | iEvaluate - copied to proxy 164 | execute expression supplied to isolate 165 | 166 | ⍺ | ⍵ - n is the syntax code supplied by "syntax" 167 | | 168 | | 2 n arrayname 169 | | 2 n arrayname newvalue 170 | | 2 n arrayname (PropertyArguments : Indexers IndexersSpecified) 171 | | 2 n arrayname (PropertyArguments : Indexers IndexersSpecified 172 | | NewValue) 173 | | 3 n niladname 174 | | 3 n (expression) 175 | | 3 n {monad} rarg 176 | larg | 3 n {dyad} rarg 177 | | 3 n monadname rarg 178 | larg | 3 n dyadname rarg 179 | 180 | RPCServer only permits a monadic fname and rarg to an existing function in the remote ws so we give it 'execute' with nested vector rarg as: 181 | a | b | c | d | e 182 | 0 | array | | | 183 | 0 | nilad | | | 184 | 0 | (expr) | | | 185 | 1 | monad | rarg | | 186 | 2 | larg | dyad | rarg | 187 | 3 | array | value | | 188 | 4 | array | indices | axes | 189 | 5 | array | indices | axes | value 190 | 191 | If the expression causes an error that is trapped in the remote 192 | process it is passed back as ⎕EN ⎕DM that is signalled . 193 | -------------------------------- 194 | Configuration options 195 | (from the setDefaults private function: 196 | ,------------------------------------------------------------------------------, 197 | | options.debug←0 ⍝ cut back on error | 198 | | options.drc←# ⍝ copy into # if # and missing | 199 | | options.workspace←'isolate' ⍝ load current ws for remotes? | 200 | | options.listen←0 ⍝ can isolate call back to ws | 201 | | options.processors←processors 4 ⍝ no. processors | 202 | | options.processes←1 ⍝ per processor | 203 | | options.runtime←'R'∊3⊃'.'⎕WG'APLVersion' ⍝ use runtime version unless devt| 204 | | options.status←'client' ⍝ set as 'server' by StartServer | 205 | '------------------------------------------------------------------------------' 206 | debug: 207 | All public fns and ops set an error guard as: 208 | trapErr''::signal'' 209 | If options.debug is 0 (the default) then trapErr'' returns 0 so all errors are trapped and signalled back to the session. 210 | If 1 then trapErr'' is ⍬, there is no trap and errors are signalled at the point of error. 211 | 212 | drc: 213 | If drc is # (the default) we check to see if #.DRC exists and copy if not. Otherwise drc must be a ref to the extant DRC namespace. 214 | 215 | workspace: 216 | This is the workspace to be ⎕LOADed by APLProcess (default "isolate") that must contain the isolate namespace and call 'isolate.ynys.isoStart ⍬' in its ⎕LX. It should be in one of the folders defined in [Options]-[Configure...]-[Workspace] in the session menubar or be specified with a full path. 217 | 218 | listen: 219 | Whether the isolates will be enabled to call back to the active ws to request further data &c. (default 0.) 220 | When enabled an "instance" of RPCServer is created locally to receive requests from the isolates. 221 | Such requests are issued to the "parent" of the remote isolate accessed as ##. which is enabled as an isolate in its own right. 222 | 223 | processors: 224 | The number of prosessors in the machine. Currently available from Windows but default 4 elsewhere. 225 | 226 | processes: 227 | The number of processes that will be started per prosessor in the machine. (default 1.) 228 | 229 | runtime: 230 | Whether the runtime equivalent of the active interpreter should be started for the slave processes. If the active interpreter (the default) is runtime then runtime anyway. 231 | 232 | status: 233 | Set by StartServer and AddServer. 234 | 235 | -------------------------------------------------------------------------------- /Source/isolate/APLProcess.dyalog: -------------------------------------------------------------------------------- 1 | :Class APLProcess 2 | ⍝ Start (and eventually dispose of) an APL process 3 | 4 | (⎕IO ⎕ML)←1 1 5 | 6 | ∇ r←Version 7 | :Access Public Shared 8 | r←'APLProcess' '2.4.0' '2025-12-09' 9 | ∇ 10 | 11 | :Field Public Args←'' 12 | :Field Public Ws←'' 13 | :Field Public Exe←'' 14 | :Field Public Proc 15 | :Field Public onExit←'' 16 | :Field Public RunTime←0 ⍝ Boolean or name of runtime executable 17 | :Field Public IsSsh 18 | :Field Public RideInit←'' 19 | :Field Public Load←'' 20 | :Field Public Lx←'' 21 | :Field Public OutFile←'' 22 | :Field Public WorkingDir←'' 23 | :Field Public Detach←0 24 | 25 | :property Id 26 | :Access public 27 | ∇ r←Get ipa 28 | r←{6::'' ⋄ Proc.Id}'' 29 | ∇ 30 | :endproperty 31 | 32 | ∇ make 33 | :Access public instance 34 | :Implements constructor 35 | make_common 36 | ∇ 37 | 38 | ∇ make1 args;rt;cmd;ws;params;ns;invalid;settings 39 | :Access Public Instance 40 | :Implements Constructor 41 | ⍝ args is: 42 | ⍝ [1] the workspace to load 43 | ⍝ [2] any command line arguments 44 | ⍝ {[3]} if present, a Boolean indicating whether to use the runtime version, OR a character vector of the executable name to run 45 | ⍝ {[4]} if present, the RIDE_INIT parameters to use or RIDE port for SERVE mode 46 | ⍝ {[5]} if present, a log-file prefix for process output 47 | ⍝ {[6]} if present, the "current directory" when APL is started 48 | ⍝ {[7]} if present, and set to 1, do not kill spawned process in destructor 49 | ⍝ {[8]} if present, is the LOAD= setting for spawned process 50 | ⍝ {[9]} if present, is the LX= setting for the spawned process 51 | make_common 52 | args←(eis⍣({9.1≠⎕NC⊂,'⍵'}⊃args)⊢args) 53 | :Select {⊃⎕NC⊂,'⍵'}⊃args 54 | :Case 2.1 ⍝ array 55 | (Ws Args RunTime RideInit OutFile WorkingDir Detach Load Lx)←9↑args,(⍴args)↓Ws Args RunTime RideInit OutFile WorkingDir Detach Load Lx 56 | :Case 9.1 ⍝ namespace 57 | :If 0∊⍴invalid←(settings←args.⎕NL ¯2.1 ¯9.1)~(⎕NEW⊃⊃⎕CLASS ⎕THIS).⎕NL ¯2.2 58 | args{⍎⍵,'←⍺⍎⍵'}¨settings 59 | :Else ⋄ ('Invalid APLProcess setting(s): ',,⍕invalid)⎕SIGNAL 11 60 | :EndIf 61 | :Else ⋄ 'Invalid constructor argument'⎕SIGNAL 11 62 | :EndSelect 63 | :If 'New'≢2⊃⎕SI,⊂'' ⍝ do not autostart if using APLProcess.New 64 | {}Run 65 | :EndIf 66 | ∇ 67 | 68 | ∇ make_common 69 | Proc←⎕NS'' ⍝ Do NOT do this in the field definition 70 | IsSsh←0 71 | WorkingDir←1⊃1 ⎕NPARTS'' ⍝ default directory 72 | PATH←SourcePath 73 | ∇ 74 | 75 | ∇ r←New args 76 | :Access public shared 77 | :If 0∊⍴args 78 | r←##.⎕NEW ⎕THIS 79 | :Else 80 | r←##.⎕NEW ⎕THIS args 81 | :EndIf 82 | ∇ 83 | 84 | ∇ (rc msg)←Run 85 | :Access Public Instance 86 | msg←'' 87 | :Trap rc←0 88 | Start(Ws Args RunTime) 89 | :Else 90 | (rc msg)←⎕DMX.(EN EM) 91 | :EndTrap 92 | ∇ 93 | 94 | ∇ Start(ws args rt);psi;pid;cmd;host;port;keyfile;exe;z;output 95 | (Ws Args)←ws args 96 | 97 | :If 0∊⍴RideInit ⍝ Always set RIDE_INIT so that started process does not inherit this process's setting 98 | args,←' RIDE_INIT=""' 99 | :ElseIf 3=10|⎕DR RideInit ⍝ port number? 100 | args,←' RIDE_INIT="SERVE:*:',(⍕RideInit),'" RIDE_SPAWNED=1' 101 | :Else 102 | args,←' RIDE_INIT="',RideInit,'" RIDE_SPAWNED=1' 103 | :EndIf 104 | 105 | args,←' LOAD="',Load,'"' ⍝ always set LOAD as to not inherit this process's setting 106 | 107 | :If ~0∊⍴Lx 108 | args,←' LX="',Lx,'"' 109 | :EndIf 110 | 111 | :If ~0 2 6∊⍨10|⎕DR rt ⍝ if rt is character or nested, it defines what to start 112 | Exe←(RunTimeName⍣rt)GetCurrentExecutable ⍝ else, deduce it 113 | :Else 114 | Exe←rt 115 | rt←0 116 | :EndIf 117 | 118 | IsSsh←326=⎕DR Exe ⍝ ssh is denoted by nested exe (host port keyfile exe) 119 | 120 | :If IsWin>IsSsh 121 | ⎕USING←UsingSystemDiagnostics 122 | psi←⎕NEW Diagnostics.ProcessStartInfo,⊂Exe(ws,' ',args) 123 | psi.WindowStyle←Diagnostics.ProcessWindowStyle.Minimized 124 | psi.WorkingDirectory←WorkingDir 125 | 126 | :If ~0∊⍴OutFile 127 | psi.UseShellExecute←0 ⍝ this needs to be false to redirect IO (.NET Core defaults to false, .NET Framework defaults to true) 128 | psi.StandardOutputEncoding←Text.Encoding.UTF8 129 | psi.RedirectStandardOutput←1 ⍝ redirect standard output 130 | :Else 131 | psi.RedirectStandardOutput←0 132 | psi.UseShellExecute←1 133 | :EndIf 134 | 135 | Proc←Diagnostics.Process.Start psi 136 | 137 | :Else ⍝ Unix 138 | :If ~∨/'LOG_FILE'⍷args ⍝ By default 139 | args,←' LOG_FILE=/dev/null ' ⍝ no log file 140 | :EndIf 141 | 142 | cmd←(~0∊⍴WorkingDir)/'cd ',WorkingDir,'; ' 143 | :If IsSsh 144 | (host port keyfile exe)←Exe 145 | cmd,←args,' ',exe,' +s -q ',ws 146 | Proc←SshProc host port keyfile cmd 147 | :Else 148 | z←⍕GetCurrentProcessId 149 | output←(1+×≢OutFile)⊃'/dev/null'OutFile 150 | cmd,←'{ ',args,' ',Exe,' +s -q ',ws,' -c APLppid=',z,' ',output,' 2>&1 & } ; echo $!' 151 | pid←tonum⊃_SH cmd 152 | Proc.Id←pid 153 | Proc.HasExited←HasExited 154 | :EndIf 155 | Proc.StartTime←⎕NEW Time ⎕TS 156 | :EndIf 157 | ∇ 158 | 159 | ∇ Close;out 160 | :Implements Destructor 161 | :If ~Detach 162 | :If IsWin 163 | :AndIf ~0∊⍴OutFile 164 | WaitForKill 200 0.1 ⍝ don't run this in a separate thread if redirecting output on Windows 165 | :Trap 0 166 | out←Proc.StandardOutput.ReadToEnd 167 | (⊂out)⎕NPUT OutFile 1 168 | :EndTrap 169 | :Else 170 | WaitForKill&200 0.1 ⍝ otherwise run in a thread for improved throughput 171 | :EndIf 172 | :EndIf 173 | ∇ 174 | 175 | ∇ WaitForKill(limit interval);count 176 | :If (0≠⍴onExit)∧~HasExited ⍝ If the process is still alive 177 | :Trap 0 ⋄ ⍎onExit ⋄ :EndTrap ⍝ Try this 178 | 179 | count←0 180 | :While ~HasExited 181 | {}⎕DL interval 182 | count←count+1 183 | :Until count>limit 184 | :EndIf ⍝ OK, have it your own way 185 | 186 | {}Kill Proc 187 | ∇ 188 | 189 | ∇ r←GetCurrentProcessId;t 190 | :Access Public Shared 191 | :If IsWin 192 | r←⍎'t'⎕NA'U4 kernel32|GetCurrentProcessId' 193 | :ElseIf {2::0 ⋄ IsSsh}'' ⍝ instance? 194 | r←Proc.Pid 195 | :Else 196 | r←tonum⊃_SH'echo $PPID' 197 | :EndIf 198 | ∇ 199 | 200 | ∇ r←GetCurrentExecutable;⎕USING;t;gmfn 201 | :Access Public Shared 202 | :If IsWin 203 | r←'' 204 | :Trap 0 205 | 'gmfn'⎕NA'U4 kernel32|GetModuleFileName* P =T[] U4' 206 | r←⊃⍴/gmfn 0(1024⍴' ')1024 207 | :EndTrap 208 | :If 0∊⍴r 209 | ⎕USING←UsingSystemDiagnostics 210 | r←2 ⎕NQ'.' 'GetEnvironment' 'DYALOG' 211 | r←r,(~(¯1↑r)∊'\/')/'/' ⍝ Add separator if necessary 212 | r←r,(Diagnostics.Process.GetCurrentProcess.ProcessName),'.exe' 213 | :EndIf 214 | :ElseIf {2::0 ⋄ IsSsh}'' ⍝ instance? 215 | ∘∘∘ ⍝ Not supported 216 | :Else 217 | t←⊃1↓_SH'ps -o args -p ',⍕GetCurrentProcessId ⍝ AWS 218 | :If '"'''∊⍨⊃t ⍝ if command begins with ' or " 219 | r←{⍵/⍨{∧\⍵∨≠\⍵}⍵=⊃⍵}t 220 | :Else 221 | r←{⍵↑⍨¯1+1⍳⍨(¯1↓0,⍵='\')<⍵=' '}t ⍝ otherwise find first non-escaped space (this will fail on files that end with '\\') 222 | :EndIf 223 | :EndIf 224 | ∇ 225 | 226 | ∇ r←RunTimeName exe 227 | ⍝ Assumes that: 228 | ⍝ Windows runtime ends in "rt.exe" 229 | ⍝ *NIX runtime ends in ".rt" 230 | r←exe 231 | :If IsWin 232 | :If 'rt.exe'≢¯6↑{('rt.ex',⍵)[⍵⍳⍨'RT.EX',⍵]}exe ⍝ deal with case insensitivity 233 | r←'rt.exe',⍨{(~∨\⌽<\⌽'.'=⍵)/⍵}exe 234 | :EndIf 235 | :Else 236 | r←exe,('.rt'≢¯3↑exe)/'.rt' 237 | :EndIf 238 | ∇ 239 | 240 | 241 | ∇ r←KillChildren Exe;kids;⎕USING;p;m;i;mask 242 | :Access Public Shared 243 | ⍝ returns [;1] pid [;2] process name of any processes that were not killed 244 | r←0 2⍴0 '' 245 | :If ~0∊⍴kids←ListProcesses Exe ⍝ All child processes using the exe 246 | :If IsWin 247 | ⎕USING←UsingSystemDiagnostics 248 | p←Diagnostics.Process.GetProcessById¨kids[;1] 249 | p.Kill 250 | ⎕DL 1 251 | :If 0≠⍴p←(~p.HasExited)/p 252 | ⎕DL 1 253 | p.Kill 254 | ⎕DL 1 255 | :If ∨/m←~p.HasExited 256 | r←(kids[;1]∊m/p.Id)⌿kids 257 | :EndIf 258 | :EndIf 259 | :ElseIf {2::0 ⋄ IsSsh}'' ⍝ instance? 260 | ∘∘∘ 261 | :Else 262 | mask←(⍬⍴⍴kids)⍴0 263 | :For i :In ⍳⍴mask 264 | mask[i]←Shoot kids[i;1] 265 | :EndFor 266 | r←(~mask)⌿kids 267 | :EndIf 268 | :EndIf 269 | ∇ 270 | 271 | ∇ r←{all}ListProcesses procName;me;⎕USING;procs;unames;names;name;i;pn;kid;parent;mask;n;cmd;t 272 | :Access Public Shared 273 | ⍝ returns either my child processes or all processes 274 | ⍝ procName is either '' for all children, or the name of a process 275 | ⍝ r[;1] - child process number (Id) 276 | ⍝ r[;2] - child process name 277 | me←GetCurrentProcessId 278 | r←0 2⍴0 '' 279 | procName←,procName 280 | all←{6::⍵ ⋄ all}0 ⍝ default to just my childen 281 | 282 | :If IsWin 283 | ⎕USING←UsingSystemDiagnostics 284 | 285 | :If 0∊⍴procName ⋄ procs←Diagnostics.Process.GetProcesses'' 286 | :Else ⋄ procs←Diagnostics.Process.GetProcessesByName⊂procName ⋄ :EndIf 287 | :If all 288 | r←↑procs.(Id ProcessName) 289 | r⌿⍨←r[;1]≠me 290 | :Else 291 | :If 0<⍴procs 292 | unames←∪names←procs.ProcessName 293 | :For name :In unames 294 | :For i :In ⍳n←1+.=(,⊂name)⍳names 295 | pn←name,(n≠1)/'#',⍕i 296 | :Trap 0 ⍝ trap here just in case a process disappeared before we get to it 297 | parent←⎕NEW Diagnostics.PerformanceCounter('Process' 'Creating Process Id'pn) 298 | :If me=parent.NextValue 299 | kid←⎕NEW Diagnostics.PerformanceCounter('Process' 'Id Process'pn) 300 | r⍪←(kid.NextValue)name 301 | :EndIf 302 | :EndTrap 303 | :EndFor 304 | :EndFor 305 | :EndIf 306 | :EndIf 307 | :ElseIf {2::0 ⋄ IsSsh}'' ⍝ instance? 308 | ∘∘∘ 309 | :Else ⍝ Linux 310 | ⍝ unfortunately, Ubuntu (and perhaps others) report the PPID of tasks started via ⎕SH as 1 311 | ⍝ so, the best we can do at this point is identify processes that we tagged with APLppid= 312 | cmd←'ps -eo pid,args | sed -n ''2,$p''' ⍝ list process id and command line (with arguments) 313 | cmd,←(~all)/' | grep APLppid=',⍕me ⍝ is not selecting all, limit to APLProcess's my process started 314 | cmd,←(t←~0∊⍴procName)/' | grep ',procName ⍝ limit to entries with procName if it exists 315 | cmd,←' | grep -v grep' ⍝ remove "grep" entries 316 | procs←_SH cmd 317 | →0⍴⍨0∊⍴procs 318 | procs←↑(2 part deb)¨procs 319 | procs[;1]←(⊃tonum)¨procs[;1] 320 | procs⌿⍨←me≠procs[;1] ⍝ remove my task 321 | mask←1 322 | :If t 323 | mask←∨/¨(procName,' ')∘⍷¨procs[;2],¨' ' 324 | :EndIf 325 | r←mask⌿procs 326 | :EndIf 327 | ∇ 328 | 329 | ∇ r←Kill;delay 330 | :Access Public Instance 331 | r←0 ⋄ delay←0.1 332 | :Trap 0 333 | :If IsWin 334 | :If IsNetCore ⋄ Proc.Kill ⍬ ⋄ :Else ⋄ Proc.Kill ⋄ :EndIf ⍝ In .Net Core, Kill takes an argument 335 | :Repeat 336 | ⎕DL delay×~Proc.HasExited 337 | delay+←delay 338 | :Until (delay>10)∨Proc.HasExited 339 | :ElseIf {2::0 ⋄ IsSsh}'' 340 | ∘∘∘ 341 | :Else ⍝ Local UNIX 342 | {}UNIXIssueKill 3 Proc.Id ⍝ issue strong interrupt 343 | {}⎕DL 2 ⍝ wait a couple seconds for it to react 344 | :If ~Proc.HasExited←~UNIXIsRunning Proc.Id 345 | {}UNIXIssueKill 9 Proc.Id ⍝ issue strong interrupt 346 | {}⎕DL 2 ⍝ wait a couple seconds for it to react 347 | :AndIf ~Proc.HasExited←~UNIXIsRunning Proc.Id 348 | :Repeat 349 | ⎕DL delay 350 | delay+←delay 351 | :Until (delay>10)∨Proc.HasExited←~UNIXIsRunning Proc.Id 352 | :EndIf 353 | :EndIf 354 | r←Proc.HasExited 355 | :EndTrap 356 | ∇ 357 | 358 | ∇ r←Shoot Proc;MAX;res 359 | MAX←100 360 | r←0 361 | :If 0≠⎕NC⊂'Proc.HasExited' 362 | :Repeat 363 | :If ~Proc.HasExited 364 | :If IsWin 365 | :If IsNetCore ⋄ Proc.Kill ⍬ ⋄ :Else ⋄ Proc.Kill ⋄ :EndIf 366 | ⎕DL 0.2 367 | :ElseIf {2::0 ⋄ IsSsh}'' ⍝ instance? 368 | ∘∘∘ 369 | :Else 370 | {}UNIXIssueKill 3 Proc.Id ⍝ issue strong interrupt AWS 371 | {}⎕DL 2 ⍝ wait a couple seconds for it to react 372 | Proc.HasExited←~UNIXIsRunning Proc.Id ⍝ AWS 373 | :EndIf 374 | :EndIf 375 | MAX-←1 376 | :Until Proc.HasExited∨MAX≤0 377 | r←Proc.HasExited 378 | :ElseIf 2=⎕NC'Proc' ⍝ just a process id? 379 | {}UNIXIssueKill 9 Proc 380 | {}⎕DL 2 381 | r←~UNIXIsRunning Proc 382 | :EndIf 383 | ∇ 384 | 385 | ∇ r←HasExited 386 | :Access public instance 387 | :If IsWin∨{2::0 ⋄ IsSsh}'' 388 | r←{0::⍵ ⋄ Proc.HasExited}1 389 | :Else 390 | Proc.HasExited←r←{0::⍵ ⋄ ~UNIXIsRunning Proc.Id}1 ⍝ AWS 391 | :EndIf 392 | ∇ 393 | 394 | ∇ r←GetExitCode 395 | :Access public Instance 396 | ⍝ *** EXPERIMENTAL *** 397 | ⍝ query exit code of process. Attempt to do it in a cross platform way relying on .Net Core. Unfortunetaly 398 | ⍝ we only use it on Windows atm, so this method can only be used on Windows. 399 | r←'' ⍝ '' indicates "can't check" (for example, because it is still running) or non-windows platform 400 | :If HasExited 401 | :If IsWin 402 | r←Proc.ExitCode 403 | :Else 404 | :EndIf 405 | :EndIf 406 | ∇ 407 | 408 | ∇ r←IsRunning args;⎕USING;start;exe;pid;proc;diff;res 409 | :Access public shared 410 | ⍝ args - pid {exe} {startTS} 411 | r←0 412 | args←eis args 413 | (pid exe start)←3↑args,(⍴args)↓0 ''⍬ 414 | :If IsWin 415 | ⎕USING←UsingSystemDiagnostics 416 | :Trap 0 417 | proc←Diagnostics.Process.GetProcessById pid 418 | r←~proc.HasExited 419 | :Else 420 | :Return 421 | :EndTrap 422 | :If ''≢exe 423 | r∧←exe≡proc.ProcessName 424 | :EndIf 425 | :If ⍬≢start 426 | :Trap 90 427 | diff←|-/DateToIDN¨start(proc.StartTime.(Year Month Day Hour Minute Second Millisecond)) 428 | r∧←diff≤24 60 60 1000⊥0 1 0 0÷×/24 60 60 1000 ⍝ consider it a match within a 1 minute window 429 | :Else 430 | r←0 431 | :EndTrap 432 | :EndIf 433 | :ElseIf {2::0 ⋄ IsSsh}'' 434 | ∘∘∘ 435 | :Else 436 | r←UNIXIsRunning pid 437 | :EndIf 438 | ∇ 439 | 440 | ∇ r←Stop pid;proc 441 | :Access public shared 442 | ⍝ attempts to stop the process with processID pid 443 | :If IsWin 444 | ⎕USING←UsingSystemDiagnostics 445 | :Trap 0 446 | proc←Diagnostics.Process.GetProcessById pid 447 | :Else 448 | r←1 449 | :Return 450 | :EndTrap 451 | :If IsNetCore ⋄ proc.Kill ⍬ ⋄ :Else ⋄ proc.Kill ⋄ :EndIf 452 | {}⎕DL 0.5 453 | r←~IsRunning pid 454 | :ElseIf {2::0 ⋄ IsSsh}'' ⍝ instance? 455 | ∘∘∘ 456 | :Else 457 | r←UnixKill pid 458 | :EndIf 459 | ∇ 460 | 461 | ∇ r←UNIXIsRunning pid;txt 462 | ⍝ Return 1 if the process is in the process table and is not a defunct 463 | :If {2::0 ⋄ IsSsh}'' ⍝ instance? 464 | ∘∘∘ 465 | :Else 466 | :If IsAIX 467 | txt←⊃_SH'ps -p "',(⍕pid),'" -o s=' 468 | :Else 469 | txt←⊃_SH'ps -p "',(⍕pid),'" -o state=' 470 | :EndIf 471 | r←(~'Z'∊txt)∧(∨/txt≠' ')∧0<≢txt 472 | :EndIf 473 | ∇ 474 | 475 | ∇ {r}←UnixKill pid;delay;t 476 | {}UNIXIssueKill 3 pid ⍝ issue strong interrupt 477 | {}⎕DL 2 ⍝ wait a couple seconds for it to react 478 | :If t←UNIXIsRunning pid ⍝ still running? 479 | {}UNIXIssueKill 9 pid ⍝ bring out the heavy guns 480 | {}⎕DL 2 ⍝ wait a couple seconds for it to react 481 | :AndIf t←UNIXIsRunning pid 482 | delay←0.2 483 | :Repeat 484 | ⎕DL delay 485 | delay+←delay 486 | :Until (delay>5)∨t←UNIXIsRunning pid ⍝ keep checking (doubling the wait) up to 5 seconds 487 | :EndIf 488 | r←~t 489 | ∇ 490 | 491 | 492 | ∇ {r}←UNIXIssueKill(signal pid) 493 | signal pid←⍕¨signal pid 494 | cmd←'kill -',signal,' ',pid,' >/dev/null 2>&1 ; echo $?' 495 | :If {2::0 ⋄ IsSsh}'' ⍝ instance? 496 | ∘∘∘ 497 | :Else 498 | r←⎕SH cmd 499 | :EndIf 500 | ∇ 501 | 502 | ∇ r←UNIXGetShortCmd pid 503 | ⍝ Retrieve short form of cmd used to start process 504 | :If {2::0 ⋄ IsSsh}'' ⍝ instance? 505 | ∘∘∘ 506 | :Else 507 | :Trap 11 508 | r←⊃1↓_SH'ps -o comm -p ',⍕pid 509 | :Else 510 | r←'' 511 | :EndTrap 512 | :EndIf 513 | ∇ 514 | 515 | ∇ r←_SH cmd 516 | :Access public shared 517 | r←{0::'' ⋄ ⎕SH ⍵}cmd 518 | ∇ 519 | 520 | :Class Time 521 | :Field Public Year 522 | :Field Public Month 523 | :Field Public Day 524 | :Field Public Hour 525 | :Field Public Minute 526 | :Field Public Second 527 | :Field Public Millisecond 528 | 529 | ∇ make ts 530 | :Implements Constructor 531 | :Access Public 532 | (Year Month Day Hour Minute Second Millisecond)←7↑ts 533 | ⎕DF(⍕¯2↑'00',⍕Day),'-',((12 3⍴'JanFebMarAprMayJunJulAugSepOctNovDec')[⍬⍴Month;]),'-',(⍕100|Year),' ',1↓⊃,/{':',¯2↑'00',⍕⍵}¨Hour Minute Second 534 | ∇ 535 | 536 | :EndClass 537 | 538 | ∇ r←ProcessUsingPort port;t 539 | ⍝ return the process ID of the process (if any) using a port 540 | :Access public shared 541 | r←⍬ 542 | :If IsWin 543 | :If ~0∊⍴t←_SH'netstat -a -n -o' 544 | :AndIf ~0∊⍴t/⍨←∨/¨'LISTENING'∘⍷¨t 545 | :AndIf ~0∊⍴t/⍨←∨/¨((':',⍕port),' ')∘⍷¨t 546 | r←∪∊¯1↑¨(//)∘⎕VFI¨t 547 | :EndIf 548 | :Else 549 | :If ~0∊⍴t←_SH'netstat -l -n -p 2>/dev/null | grep '':',(⍕port),' ''' 550 | r←∪∊{⊃(//)⎕VFI{(∧\⍵∊⎕D)/⍵}⊃¯1↑{⎕ML←3 ⋄ (' '≠⍵)⊂⍵}⍵}¨t 551 | :EndIf 552 | :EndIf 553 | ∇ 554 | 555 | ∇ r←MyDNSName;GCN 556 | :Access Public Shared 557 | 558 | :If IsWin 559 | 'GCN'⎕NA'I4 Kernel32|GetComputerNameEx* U4 >0T =U4' 560 | r←2⊃GCN 7 255 255 561 | :Return 562 | ⍝ ComputerNameNetBIOS = 0 563 | ⍝ ComputerNameDnsHostname = 1 564 | ⍝ ComputerNameDnsDomain = 2 565 | ⍝ ComputerNameDnsFullyQualified = 3 566 | ⍝ ComputerNamePhysicalNetBIOS = 4 567 | ⍝ ComputerNamePhysicalDnsHostname = 5 568 | ⍝ ComputerNamePhysicalDnsDomain = 6 569 | ⍝ ComputerNamePhysicalDnsFullyQualified = 7 <<< 570 | ⍝ ComputerNameMax = 8 571 | :ElseIf {2::0 ⋄ IsSsh}'' ⍝ instance? 572 | ∘∘∘ ⍝ Not supported 573 | :Else 574 | r←⊃_SH'hostname' 575 | :EndIf 576 | ∇ 577 | 578 | ∇ idn←DateToIDN ts 579 | idn←(2 ⎕NQ'.' 'DateToIDN'(3↑ts))+(24 60 60 1000⊥4↑3↓ts)÷86400000 580 | ∇ 581 | 582 | ∇ Proc←SshProc(host user keyfile cmd);conn;z;kf;allpids;guid;listpids;pids;⎕USING;pid;tid 583 | ⎕USING←'Renci.SshNet,',PATH,'/Renci.SshNet.dll' 584 | kf←⎕NEW PrivateKeyFile(,⊂keyfile) 585 | conn←⎕NEW SshClient(host 22 user(,kf)) 586 | 587 | :Trap 0 588 | conn.Connect ⍝ This is defined to be a void() 589 | :Case 90 ⋄ ('Error creating ssh client instance: ',⎕EXCEPTION.Message)⎕SIGNAL 11 590 | :Else ⋄ 'Unexpected error creating ssh client instance'⎕SIGNAL 11 591 | :EndTrap 592 | 593 | listpids←{0~⍨2⊃(⎕UCS 10)⎕VFI(conn.RunCommand⊂'ps -u ',user,' | grep dyalog | grep -v grep | awk ''{print $2}''').Result} 594 | guid←'dyalog-ssh-',(⍕⎕TS)~' ' 595 | pids←listpids ⍬ 596 | Proc←⎕NS'' 597 | Proc.SshConn←conn 598 | Proc.HasExited←0 599 | tid←{SshRun conn ⍵ Proc}&⊂cmd 600 | Proc.tid←tid 601 | ⎕DL 1 602 | :If 1=⍴pid←(listpids ⍬)~pids ⋄ pid←⊃pid 603 | :Else ⋄ ∘∘∘ ⋄ :EndIf ⍝ failed to start 604 | Proc.Pid←pid 605 | ∇ 606 | 607 | ∇ SshRun(conn cmd proc) 608 | ⍝ Wait until APL exits, then set HasExited←1 609 | conn.RunCommand cmd 610 | proc.HasExited←1 611 | ∇ 612 | 613 | :section Utils 614 | 615 | endswith←{w←,⍵ ⋄ a←,⍺ ⋄ w≡(-(⍴a)⌊⍴w)↑a} 616 | tonum←{⊃⊃(//)⎕VFI ⍵} 617 | eis←{2>|≡⍵:,⊂⍵ ⋄ ⍵} ⍝ enclose if simple 618 | deb←{1↓¯1↓{⍵/⍨~' '⍷⍵}' ',⍵,' '} ⍝ delete extraneous blanks 619 | part←{⍵⊆⍨~⍺{⍵∧⍺>+\⍵}' '=⍵} ⍝ partition first ⍺ sections 620 | nameClass←{⎕NC⊂,'⍵'} ⍝ name class of argument 621 | 622 | ∇ r←IsWin 623 | :Access public shared 624 | r←'Win'≡Platform 625 | ∇ 626 | 627 | ∇ r←IsMac 628 | :Access public shared 629 | r←'Mac'≡Platform 630 | ∇ 631 | 632 | ∇ r←IsAIX 633 | :Access public shared 634 | r←'AIX'≡Platform 635 | ∇ 636 | 637 | ∇ r←Platform 638 | :Access public shared 639 | r←3↑⊃#.⎕WG'APLVersion' 640 | ∇ 641 | 642 | ∇ r←IsNetCore 643 | :Access public shared 644 | :Trap 11 ⍝ DOMAIN ERROR if 2250⌶ isn't available 645 | r←1 1≡2↑2250⌶0 ⍝ 2250⌶0 is the preferred mechanism to interrogate .NET availability 646 | :Else 647 | r←(,'1')≡2 ⎕NQ'.' 'GetEnvironment' 'DYALOG_NETCORE' 648 | :EndTrap 649 | ∇ 650 | 651 | ∇ r←UsingSystemDiagnostics 652 | :Access public shared 653 | r←(1+IsNetCore)⊃'System,System.dll' 'System,System.Diagnostics.Process' 654 | ∇ 655 | 656 | ∇ path←SourcePath;source 657 | ⍝ Determine the source path of the class 658 | :Trap 6 659 | source←⍎'(⊃⊃⎕CLASS ⎕THIS).SALT_Data.SourceFile' ⍝ ⍎ works around a bug 660 | :Else 661 | :If 0=⍴source←{((⊃¨⍵)⍳⊃⊃⎕CLASS ⎕THIS)⊃⍵,⊂''}5177⌶⍬ ⍝ 5177⌶⍬ - list loaded file objects 662 | source←⎕WSID 663 | :Else ⋄ source←4⊃source 664 | :EndIf 665 | :EndTrap 666 | path←{(-⌊/(⌽⍵)⍳'\/')↓⍵}source 667 | ∇ 668 | 669 | :endsection 670 | 671 | :EndClass 672 | --------------------------------------------------------------------------------