User:Devon McCormick/ParallelizedJCodeExamples

From J Wiki
Jump to navigation Jump to search

Examples of Parallel Code in J

This contains complete examples of code presented at the APL2010 conference in Berlin, Germany, in September, 2010. There are two examples of parallel code tasks as well as an implementation of mutex (mutual exclusion), all written in the J programming language (version 6.02). A copy of the J programming environment is freely available from jsoftware.com. When attempting to run this code, be aware that some longer lines may have “wrapped”.

Immediately below is the first task, the original “photo flipping” code. The customizations for my site should be contained in the first few lines of globals’ assignment near the top. There are a number of utility functions here for ancillary uses but the core function is regflipphotos.

Examples of the parallelizing extensions to this code are contained below as well. When this script is loaded, an anonymous noun is displayed by the section of code beginning “smoutput 0 : 0” and ending with the bare “)” at the end of that short paragraph. This displays examples of some of the most commonly used phrases in our J session when we load this script so that frequently-used invocations can simply be selected and run in the session.

So, as typically used, I would select the line

   info,<6!:2 'info=: getFrom1Drive ''F:\'''

to get all the photos from directories on the camera memory chip available as the “F:” drive, assuming a standard set of directory names that may vary by camera model. The “info” and “6!:2” parts assign information about the photos to a variable for later use and time the transfer, respectively. Currently these names are set for a Fuji FinePix which creates numbered sub-directories with names starting with “DSCF” under a directory “DCIM”. A Nikon camera might create numbered directories starting with “DSCN”.

After this function moves the pictures from the camera chip to a sub-directory named by the date range of pictures obtained, I would run some variant of the line

   4 parcelOutFlipping dd=. endSlash '"'-.~>0{info   NB.  4 parallel flippers

to spin off four independent processes to flip the photos in the recently-created directory. Typically I have been using the left argument “2” instead of “4” because my current machine has two cores and this seems logical. However, I’ve been running with four parallel processes lately to gather realistic timings to evaluate the efficiency of running more processes than there are cores.

[More recently, I've reverted to invoking only two processes because the four-process version seemed to occasionally lock up my PC.]

Parallel Photo Flipping

NB.* savePhotoDirInfo.ijs: save dir info of photos on camera/chip, copy pix.

load 'csv task filefns images'
coinsert 'fldir'
PHOTOP=: 'c:\amisc\pix\Photos\'  NB. Top dir where I keep photos.
jpeg_quality_ima3_=: 99          NB. Save .jpgs as 99% lossless.

smoutput 0 : 0           NB. Remind us of usual use.
info,<6!:2 'info=: getFrom1Drive ''F:\'''
info,<6!:2 'info=: getFrom1Drive ''C:\amisc\'''   NB. This or preceding...
6!:2 'regflipphotos ''"''-.~>0{info'              NB. Single-thread or
4 parcelOutFlipping dd=. endSlash '"'-.~>0{info   NB.  4 parallel flippers
fsize dd,'time.out'
ratioKept >0{info
)

NB.* jpgSzs: give size and number of .jpg files in dir y.
jpgSzs=: 13 : '(+/,#);2{"1 dir ''*.jpg'',~endSlash y'

NB.* szRatio: give files sizes, ratio of photos discarded and kept.
szRatio=: (jpgSzs,ratioKept)
szRatio_eguse_=: 0 : 0
   TD=. 'C:\amisc\pix\Photos\2010Q2\'
   kr=. (3 : 'y(],.[:szRatio&.>],&.>~[:<[) jd dir ''*'',~y=. endSlash y') TD
   ((0{"1 kr),.":&.>&>1{"1 kr) writecsv 'KeptRatio2010Q2.csv'
)

NB.* evenlyPartition: evenly partition y into x pieces w/smaller at end.
evenlyPartition=: 4 : '(x(([:i.]) e. ([:i.[) * [:>.%~)#y)<;.1 y'

NB.* evenlyPartitionBy: evenly partition y into (>0{x) pieces by >1{x amts.
evenlyPartitionBy=: 4 : '(+/ (+/\>1{x) >/~ (}.i.>0{x)*(>0{x)%~+/>1{x) </. y'

NB.* delFlIfExist: delete file if it exists.
delFlIfExist=: 3 : 'if. fexist y do. ferase y end.'

NB.* parcelOutFlipping: run x separate processes to flip photos in dir y.
parcelOutFlipping=: 4 : 0
   'fls szs'=. |:0 2{"1 dir '*.jpg',~y=. endSlash y
   szs=. ;szs [ fls=. <"1 fls
NB.   fls=. x evenlyPartition fls  NB. Put filenames into evenly-divided lists.
   fls=. (x;szs) evenlyPartitionBy fls  NB. % filenames evenly by file size.
   scrfls=. (<'.ijs'),~&.>(<'FlipScript'),&.>":&.>i.x
   delFlIfExist&.>scrfls
   1!:44 y [ svdir=. 1!:43 ''      NB. Move to target dir to flip photos.
   (<fread jpath '~Code/sampFlipPhotos.ijs') fwrite&.>scrfls
   (<y) appendArgsToScript&.>scrfls;&.><"0 fls
   exe=. (<'"',(jpath '~bin'),'\j.exe" -jijx "'),&.><endSlash 1!:43 ''
   fork&.>exe,&.>scrfls,&.>'"'
   1!:44 svdir
NB.EG parcelOutFlipping '"'-.~>0{info
)

NB.* appendArgsToScript: for photo directory x, add filename info to scripts.
appendArgsToScript=: 4 : 0
   'scrflnm flnms'=. y   NB. Script file names, photo file names
   (LF,~LF,~'IPD=: <''',x,'''') fappend scrflnm   NB. Photo dir as global
   (')',~;LF,~&.>'PHFLS=: <;._2]0 : 0';dltb&.>flnms) fappend scrflnm
   (LF,LF,~'onlyRuntime ''''') fappend scrflnm    NB. Run code if standalone.
)

NB.EG Append code like this, with different "PHFLS", to multiple script files.
NB. IPD=: 'c:\amisc\pix\Photos\2010Q1\20100311\'
NB. PHFLS=: 0 : 0
NB. DSCF3837.jpg
NB. DSCF3838.jpg
NB. DSCF3839.jpg
NB. )

NB.* regflipphoto: flip a .jpg pic 1/4 counterclockwise as is most common.
regflipphoto=: 3 : 0
   (1 0 2|:|."2 a.{~read_image y) write_image y
NB.EG
)

NB.* regflipphotos: flip all .jpg in dir 1/4 counterclockwise.
regflipphotos=: 3 : 0
   y=. endSlash y
   regflipphoto&.>(<y),&.>{."1 dir y,'*.jpg'
NB.EG 6!:2 'regflipphotos ''c:\pix\'''
)

NB.* getFrom1Drive: get photos from drive y -> \amisc\pix\photos\[yyyyQq] year and quarter
getFrom1Drive=: 3 : 0
   phd=. endSlash y
   topdirs=. dir phd,'*.'
NB. +----+------------------+-+---+------+
NB. |DCIM|2008 6 22 13 28 44|0|rw-|----d-|   NB. Example top-level photo dir
NB. +----+------------------+-+---+------+
   if. (<'DCIM') e. {."1 topdirs do. getPixDir phd,'DCIM\' else. 0 end.
NB.EG getFrom1Drive 'F:\'
NB.EG info;]6!:2 'info=: getFrom1Drive ''E:\'''
)

NB.* getPixDir: get pictures from directory on camera assuming standard names.
getPixDir=: 3 : 0
NB.   subds=. {."1 dir phd,'DCIM\*.'
   subds=. {."1 dir '*.',~y=. endSlash y
NB. +--------+--------+
NB. |102_FUJI|101_FUJI|                      NB. Example photo sub-dirs
NB. +--------+--------+
   'destdir sumszs'=. accumSvInfo y
   cmd=. (<'move "'),&.>(<y),&.>subds,&.><'\*.jpg" ',destdir
   cmd=. cmd,(<'move "'),&.>(<y),&.>subds,&.><'\*.avi" ',destdir
   rr=. shell&>cmd
   rr=. >,&.>/_3{.&.><;._2&.>rr
   destdir;(ratioKept destdir-.'"');sumszs
NB.EG 'destdir ratios totsz'=. getPixDir 'H:\DCIM\'
)

NB.* accumSvInfo: accumulate picture file information & save before altering.
accumSvInfo=: 3 : 0
   inf=. >,&.>/dir&.>(<y),&.>({."1 dir y,'*.'),&.><'\*.jpg'
   inf=. inf,>,&.>/dir&.>(<y),&.>({."1 dir y,'*.'),&.><'\*.avi'
   inf=. inf/:1{"1 inf                       NB. Order by ascending date
NB. +----------------------+----------------------+
NB. |G:\DCIM\102_FUJI\*.jpg|G:\DCIM\101_FUJI\*.jpg|
NB. +----------------------+----------------------+
   dtstr=. combineDates >~.3{.&.>1{"1 inf    NB. Date string, e.g. 20080628-29
   initDt=. 2{.>(<0 1){ inf                  NB. Characterize by initial date
   dsd=. (":0{initDt),'Q',":>.3%~1{initDt    NB. yyyyQq, e.g. 2008Q3<-3rd qtr 2008
   shell 'mkdir ',destdir=. '"','"',~PHOTOP,dsd,'\',dtstr
   saveInfoFl destdir;<(":&.>_2}."1 inf)     NB. Info file->new dir
   destdir;+/;2{"1 inf                       NB. Sum file sizes
)

NB.* ary2csv v represent enclosed array in csv format.
ary2csv=: 3 : 0
   ;(<@(,&LF)@}:@;)"1 ('"'&,@(,&'",')@(#~ >:@(=&'"'))) &.> ,&.> 8!:2 &.> y
)

NB.* saveInfoFl: save file of information on file sizes, save dates and times.
saveInfoFl=: 3 : 0
   'ddir inf'=. y
   ifl=. (ddir-.'"'),'\info.csv'   NB. Append w/o header if file exists.
   (ary2csv (fexist ifl)}.inf,~'Name';'Date';'Size') fappend ifl
   ifl
)

NB.* ratioKept: estimate ratio of photos taken to those retained.
ratioKept=: 3 : 0
   if. 1=#$y do.              NB. Single, enclosed item is dir name.
       fls=. {."1 jfi dir (y-.'"'),'\*.jpg'
   else. fls=. {."1 y end.
   if. 0=#nn=. countPicNums fls do. 0 0 0 else.
   (|0 1-(#nn)%>:(>./-<./)nn),#fls end.      NB. Ratio kept, discarded; # files
)

countPicNums=: 3 : 0
   nn=. (4{. 4}.])&.>y        NB. Get picture nums from file names.
   nn=. ".&>nn#~isValNum&>nn  NB. Only numbers
NB. 6667 and 3333 are arbitrary to catch break from 9999 -> 0 rollover.
   if. ((6667+./ .>~])*. 3333+./ .<~]) nn do. nn=. nn+10000*nn<:6667 end.
)

NB.* combineDates: combine list of dates into string from earliest-latest,
NB. combining common high-order date parts; see examples below.
combineDates=: 3 : 0
   dtlst=. ({.,:{:)4 _2 _2{.&.>"1]4 lead0s&.>/:~(_2{.1,$y)$,y
   deq=. *./\=/dtlst   NB. Where leading parts of dates are equal
   dtstr=. ;(deq#0{dtlst),}:,(<'-'),.~(-.deq)#"1 dtlst
   dtstr}.~-'-'={:dtstr
NB.EG    combineDates ,:2008 6 30
NB.EG 20080630
NB.EG    combineDates 2008 6 29,:2008 6 30
NB.EG 20080629-30
NB.EG    combineDates 2008 6 29,2008 6 30,2008 7 1,:2008 7 3
NB.EG 20080629-0703
NB.EG    combineDates 2008 12 30,2008 12 31,:2009 1 1
NB.EG 20081230-20090101
)

Parallel Photo Flipping Task Template

This is the template customized for each parallel task by the code above. This version implements mutex to prevent the multiple processes from over-writing each others’ information in their common timing file.

NB.* sampFlipPhotos.ijs: sample dedicated photo flipper.

load 'task images filefns ~Code/mutex.ijs'
coinsert 'fldir' [ coinsert 'mutex'
jpeg_quality_ima3_=: 99

NB.* regflipphoto: flip a .jpg pic 1/4 counterclockwise as is most common.
regflipphoto=: 3 : 0
   (1 0 2|:|."2 a.{~read_image y) write_image y
NB.EG
)

onlyRuntime=: 3 : 0
NB.* onlyRuntime: only invoke if not loaded via interactive session.
   if. (5{.&.>'-jijx';<'-rt') +./ . e. 5&{.&.>tolower&.>ARGV_z_ do.
       1!:44 >IPD [ arg=. 1|.'""',}.;' ',&.>ARGV_z_ [ flout=. 'time.out'
       getHold ''
       (LF,~'Start flips (for ',arg,') @ ',":qts'') fappend flout
       releaseHold ''
       tm=. ":6!:2 'regflipphoto&.>IPD,&.>PHFLS'
       getHold ''
       (LF,~'Finished flips (for ',arg,') in ',tm,' seconds @ ',":qts'') fappend flout
       releaseHold ''
       2!:55 ''
   end.
)

Parallel Directory Parsing

The following code demonstrates another task run in parallel. This code parses the contents of a specified set of files, usually starting from the root of a hard-drive, to create several global vectors of information about all the files and directories under the starting point. These vectors can be used for a number of things but are most commonly used to implement a flexible backup routine which allows us to copy a certain amount of the most recently-changed files.

There are three scripts here. The main one, parseDir.ijs contains the original, serial directory-parsing code. The parallel extensions to this comprise two scripts: the main routine parallelParseDir.ijs which implements the parsing in parallel, and the template script pllPDSub.ijs which is customized for each parallel task by the main routine.

NB.* ParseDir.ijs: tools to parse directory listing->backup most recently changed.
NB. Will look for command-line arguments SZLIM (max bytes to backup) and TARGDIR
NB. (target directory to which to write backups).

require '~Code/locationInfo.ijs'   NB. On which machine are we running?
require '~Code/parallelParseDir.ijs'

DEBUGON=: 0         NB. Show messages as program progresses (1) or don't (0).
SESSWDW=: 0         NB. Open session window (1) or not (0)
DBGFL=: 'C:\Temp\parseDir.tmp'          NB. These 3 things allow logging at load
sepLF=: 13 : ';((": :: ])y),10{a.'      NB.  time: DBGFL (log file), sepLF, and
fappendDHM=: 4 : '(,x) (#@[ [ 1!:3) :: _1: (([: < 8 u: >) ::]) y'     NB. this.
3 : 0 ''
if. DEBUGON do. (;sepLF&.>'parseDir.ijs:13';ARGV_j_,<6!:0 '') fappendDHM DBGFL end.
)

NB. "C:\Program Files\J602\j.exe" c:\amisc\JSys\user\code\parseDir.ijs -jijx SZLIM 10e6 RUNANYDAY Y RUNFROMSAVED Y TARGDIR D:\

NB.* nameSpan: given 2 names like pfxYYYYMMDD, combine->pfxYYYYMMD1-D2.
NB.* consolidateBkpsTowardPast: copy all contents of successive dirs into
NB.* coreRunDaily: core code for "runDaily": daily backups.
NB.* buildBatFl: build .BAT file to create target dirs and copy files to them.
NB.* indicateSubdirs: from boolean selecting DIRNMS, indicate all subdirs.
NB.* rmEndSep: remove terminal path separator from string.
NB.* extractExclusions: extract names of target, exclude dirs, and files from global ExcludeUsual.
NB.* excludeFiles: exclude designated files from list to back up.
NB.* dirDependencies: convert list of full paths to index vector form of tree
NB.* getDirFlInfo: get info on dirs and files starting at node specified.
NB.* cvtDt2Num: convert Y M D h m s date to single num: YYYYMMDD.day fraction.
NB.* ExcludeUsual: list of usual files and directories to exclude from backup.
NB.* initFlsDir: parse memory-mapped file of directory listing->file, dir info.
NB.* getInfo: get directory into into file, memory-map and parse it.
NB.* process1Subdir: parse single subdir entry->files, parent dirs as globals.
NB.* extract1SubdirList: get first full sub-directory listing out of many.
NB.* addPath: put new parent/child index in tree from text of "dir\subdirs..."
NB.* Tst0addPath_tests_: test adding path to index vec tree from text.
NB.* mcopyto: text of DOS .BAT file to do multiple copies.
NB.* runDaily: backup to run every day: save some most recently changed files.
NB.* setGlobalParms: assign globals according to defaults or command-line overrides.
NB.* NYto01: convert 'N' or 'Y' to 0 or 1, respectively.
NB.* onlyRuntime: only invoke if not loaded via interactive session.
NB.* runFromSavedVars: Run backup assuming dir&file vars already saved.

NB.* nameSpan: given 2 names like pfxYYYYMMDD, combine->pfxYYYYMMD1-D2.
nameSpan=: 3 : 0
NB. y=. ({:,{.) dd  NB. Earliest, latest
   endlast=. 1 i.~ ~:/>y
   (>0{y),'-',endlast}.>1{y
)

consolidateBkpsTowardPast=: 3 : 0
NB.* consolidateBkpsTowardPast: copy all contents of successive dirs into
NB. earliest starting with oldest->consolidated backups with newest version
NB. overwriting older ones.
   y=. 2{.boxopen y           NB. Files' prefix, dir in which to work
   pfx=. openbox {.y          NB. Prefix is "WL" for laptop, "WD" desktop
   if. 0=#>1{y do.
       wrkdir=. 'C:\Temp\'
   else. wrkdir=. endSlash >1{y
   end.
   dd=. jd dir wrkdir,'*.'    NB. Just directories
   dd=. (<pfx,'[0-9]{8}$') rxfirst&.>dd   NB. Only names like {pfx}YYYYMMDD
   dd=. \:~dd-.a:             NB. Order from most to least recent.
   if. nameExists 'TOPDIR' do. svTD=. TOPDIR end.
   TOPDIR=: wrkdir
   2 moveDirOverAnother/\dd
   newnm=. nameSpan ({:,{.) dd
   cmd=. (TOPDIR{.~>:TOPDIR i. ':'),' && cd ',(TOPDIR}.~>:TOPDIR i. ':'),' && '
   cmd=. cmd,'ren "',(>{:dd),'" "',newnm,'"'
   shell cmd
   if. nameExists 'svTD' do. TOPDIR=: svTD   NB. Re-instate or
   else. 4!:55 <'TOPDIR' end.                NB.  remove global.
NB.EG consolidateBkpsTowardPast 'WD';'C:\Temp\WrkDesk\'
)

coreRunDaily=: 0 : 0
NB.* coreRunDaily: core code for "runDaily": daily backups.
   setGlobalParms ''     NB. Cmd-line size limit, target dir, if present.
   wkdy=. dow 3{.qts ''  NB. Weekday number: 0=Sunday, 6=Saturday
   wkdy e. 1 2 3 4 5     NB. Only Mon-Fri unless any
   RUNANYDAY+.wkdy e. 1 2 3 4 5
   sink=. runDaily '' [ 4!:55 <'ASSERR'
   nameExists 'ASSERR'
)

NB. Display commonly-used set of commands in usual sequence.
smoutput 0 : 0
6!:2 '''FLNMS FLDTS FLSZS FLPARENT DIRNMS DIRDEP''=. PllDirInfoEG ''C:\'''
6!:2 '''FLNMS FLDTS FLSZS FLPARENT DIRNMS DIRDEP''=: getDirFlInfo SRCDIR'
(<'\Temp\') fileVar_WS_&.>'FLNMS';'FLDTS';'FLSZS';'FLPARENT';'DIRNMS';'DIRDEP'
'batfl cmds'=. buildBatFl_parseDir_ 700e6;'C:\Temp\Recent\'
shell batfl
)

coclass 'parseDir'
require 'jmf files dir dt logger filefns task'
coinsert 'base fldir'
USUVARS=: 'FLNMS';'FLDTS';'FLSZS';'FLPARENT';'DIRNMS';'DIRDEP'
EV=: getEnviVars ''
WINDIR=: endSlash ,>EV{~<1,~(toupper&.>0{"1 EV)i.<'WINDIR'

buildBatFl=: 3 : 0
NB.* buildBatFl: build .BAT file to create target dirs and copy files to them.
   'szlim targ'=. y
   xclud=. excludeFiles targ
   dtdord=. xclud-.~\:FLDTS        NB. File list indexes in date desc. order
   toobig=. szlim<:dtdord{FLSZS    NB. Exclude any single file > size limit
   toobig=. toobig#dtdord          NB.  as this will truncate list prematurely.
   dtdord=. dtdord-.toobig
   ss=. +/\dtdord{FLSZS
   cutoff=. 1 i.~ss>szlim
NB. Do better job noting which files excluded because too big; the following
NB. fails to account for cutoff.
   if. DEBUGON do.
       pl=. 's'#~1~:#toobig
       logMsg_logger_ 'Excluding file',pl,' singly > size limit: ',":#toobig
   end.
   ix=. cutoff{.dtdord
   cmds=. (0{DIRNMS),makeCopyCmds targ;<ix   NB. 0{DIRNMS gives disk to which
   tmpd=. getTempDir ''                      NB.  to copy from.
   cmds v2f batfl=. tmpd,'CDMDCopy.bat'
   batfl;<cmds
NB.EG 'batfl cmds'=. buildBatFl 700e6;'C:\Temp\Recent\'
)

locChildren=: 3 : '(<^:(L. = 0:)y),~(<#~0~:#) I. DIRDEP e. >0{y'
indicateSubdirs=: 3 : '~.;;locChildren^:_&.>I. y'

indicateSubdirs0=: 3 : 0
NB.* indicateSubdirs: from boolean selecting DIRNMS, indicate all subdirs.
   xdix=. I. y
   childxd=. xdix-.~I. DIRDEP e. xdix
   while. 0<#childxd do.
       xdix=. ~.xdix,childxd
       childxd=. xdix-.~I. DIRDEP e. xdix
   end.
   xdix                       NB. Index into DIRNMS of all subdirectories.
NB.EG xdix=. indicateSubdirs DIRNMS e. 'c:\amisc';'c:\Program Files'
)

NB.* rmEndSep: remove terminal path separator from string.
rmEndSep=: 3 : '(]}.~[:-PATHSEP_j_={:)"1 dtb y'
extractExclusions=: 3 : 0
NB.* extractExclusions: extract names of target, exclude dirs, and files from global ExcludeUsual.
   targ=. rmEndSep y               NB. Exclude target to avoid unwanted recursion.
   sections=. '[ExcludeDirs]';'[ExcludeFiles]'
   xu=. <;._1 LF,ExcludeUsual-.CR
   xu=. xu#~&.>-.&.>+./\&.>(<'NB.')E.&.>xu  NB. Exclude comments
   xu=. xu#~0~:;#&.>xu
   whsect=. >+./&.>sections E.&.>/ xu
   secord=. /:sections i. ' '-.~&.>xu#~+./whsect
   targ;secord{(+./whsect)<;._1 xu
NB.EG 'targ xd xf'=. extractExclusions y
)

excludeFiles=: 3 : 0
NB.* excludeFiles: exclude designated files from list to back up.
   'targ xd xf'=. extractExclusions y
   whxd=. DIRNMS}.~&.>(2*;':'e.&.>DIRNMS)*;DIRNMS i.&.>':'  NB. Drop disk prefix.
   whxd=. (toupper&.>whxd)e. PATHSEP_j_,&.>toupper&.>xd,<targ    NB. Exclude target
   whxd=. (1) (indicateSubdirs whxd)}whxd                        NB.  to avoid recursion.
   whxf=. (toupper&.>FLNMS)e. toupper&.>xf                       NB. Exclude files.
   xclud=. I. whxf+.FLPARENT e. I. whxd
NB. xclud is list of indexes into FLNMS=files to exclude.
)

dirDependencies=: 3 : 0
NB.* dirDependencies: convert list of full paths to index vector form of tree
NB. showing directory and subdirectories as parent-child relations.
   odir=. y
   DIRDEP=: (#odir)$_1   NB. Dummy entry for 1st node: _1->no parent.
   for_dc. i.#odir do.
       cd=. PATHSEP_j_,~&.>dc{odir
       len=. #>cd        NB. Which prefixes match only current?
       subs=. (;cd-:&.>len{.&.>odir)*.-.;PATHSEP_j_ e.&.>len}.&.>odir
       DIRDEP=: subs}DIRDEP,:dc
   end.
NB.EG dirDependencies 'c:';'c:\t1';'c:\t1\sb1';'c:\t2';'c:\t2\sb2';'c:\t1\sb3'
NB. _1 0 1 0 3 1         NB. Parent index for each input; _1 for no parent.
)

NB.* dirInfo: put directory info in more usable format: names, dates, sizes, dir flag.
dirInfo=: ([:((0{"1]) ; ([:>1{"1]) ; ([:;2{"1]) ; [:<'d'e.&>4{"1]) dir)

getDirFlInfo=: 3 : 0
NB.* getDirFlInfo: get info on dirs and files starting at node specified.
   dskInf=. >(];[: dirInfo '\*',~]) generalWalkTree rmEndSep y
   DIRNMS=: 0{"1 dskInf                           NB. Full names of all paths
   DIRDEP=: (] i. (]{.~PATHSEP_j_ i:~])&.>) DIRNMS
   DIRDEP=: (_1) (I. DIRDEP=i.#DIRDEP)}DIRDEP     NB. Dirs' dependency tree: parent indexes
   DIRDEP=: (_1) (I. DIRDEP>:#DIRDEP)}DIRDEP      NB. "_1" is root (no parent).
   isfl=. -.;4{"1 dskInf                          NB. Exclude dir info->only files.
   FLNMS=: isfl#;1{"1 dskInf
   ned=. 0~:#&>2{"1 dskInf                        NB. No empty directories
   FLDTS=: isfl#;cvtTS21Num &.>ned#2{"1 dskInf    NB. Date as single num: YYYYMMDD.day fraction
   FLSZS=: isfl#;3{"1 dskInf                      NB. File size in bytes.
   FLPARENT=: isfl#;(#&.>3{"1 dskInf)#&.>i.#DIRNMS
   FLNMS;FLDTS;FLSZS;FLPARENT;DIRNMS;<DIRDEP
)

NB.* cvtDt2Num: convert Y M D h m s date to single num: YYYYMMDD.day fraction.
cvtDt2Num=: 3 : 0"1
   NB. 90065 = 5 + 24 60 60#.24 60 60 NB. Max secs/day+5 fudge for leap secs.
   (100#.3{.y)+90065%~24 60 60#._3{.y
NB.EG ' 20090803.32600899' -: 18j8":cvtDt2Num 2009 8 3 8 9 22
)
NB. Only distinguishes to about 1/10,000 second.

NB. Need to include a [regexp] exclusion section to apply to files, e.g. "saves-{d}*", "*~", etc.
NB. Exclude the usual files and directories from being copied given list of Files &
NB. Directories (result of munge_dir) & source Disk & target Disk[:\dir] names.
NB. Some DBs big enough to be done separately.

NB.* ExcludeUsual: list of usual files and directories to exclude from backup.
ExcludeUsual=: 0 : 0
[ExcludeDirs]
$avg
.emacs.d
CFGSAFE
C_DILLA
Documents and Settings
MSOCache
Program Files
Recycled
Recycler
WINDOWS
amisc\DCIM
i386

[ExcludeFiles]   NB. Files to exclude from any directory
~
*.bz2
*.zip
AUTOEXEC.BAT
CONFIG.SYS
IO.SYS
MSDOS.SYS
NTDETECT.COM
NTLDR
SECURITY
SOFTWARE
SYSTEM
SYSTEM.ALT
Thumbs.db
boot.ini
eventlog.log
)
NB.*To do: actually use the wildcards in the file list above!!!

NB. Replace WINNT with actual Windows system dir from environment var.
ExcludeUsual=: ('WINNT';<'\'-.~WINDIR}.~WINDIR i. '\') stringreplace ExcludeUsual

NB. -------- Dir listing fns: parse text file directory listing:
NB. -------- this is a separate way to accomplish what has been done above.
initFlsDir=: 3 : 0
NB.* initFlsDir: parse memory-mapped file of directory listing->file, dir info.
   JCHAR map_jmf_ 'DIRLSTFL';y
   DBSTR=: LF,' Directory of '
   WHDB=. (CR,DBSTR) E. DIRLSTFL   NB. Where directory breaks are
   WHDB=: (I. WHDB),<:#DIRLSTFL
   DIRNMS=: FLNMS=: ''
   FLSZS=: DIRPARENT=: FLPARENT=: i.0
   FLDTS=: 0$0.0
)

3 : 0 ''
if. DEBUGON do. (;sepLF&.>'parseDir.ijs:383';<6!:0 '') fappendDHM DBGFL end.
)

getInfo=: 3 : 0
NB.* getInfo: get directory into into file, memory-map and parse it.
NB.   winexec 'cmd /C dir /A /S C:\ > C:\allfls2.dir';1
   dirlstfl=. y
   if. 0=#dirlstfl do.
       dirlstfl=. 'C:\allfls2.dir'
       shell 'dir /A /S C:\ > ',dirlstfl
   end.
   initFlsDir dirlstfl
   for_ix. i.<:#WHDB do.
       ch=. extract1SubdirList WHDB{~ix+0 1
       process1Subdir ch
   end.
   unmapall_jmf_ ''
)

process1Subdir=: 3 : 0
NB.* process1Subdir: parse single subdir entry->files, parent dirs as globals.
   ch=. y
   thisdir=. (($DBSTR)}.ch) {.~ 1 i.~ LF E. ($DBSTR)}.ch
   'isnew thisdn'=. addPath thisdir
   ch=. (<;._1 ch)-.a:             NB. break into lines; no empty lines
   ch=. ch#~' '~:;{.&.>ch          NB. Get rid of lines beginning with space.
   ch=. <;._1&.>' ',&.>dsp&.>ch    NB. break apart lines by spaces
NB. re-join any names with embedded spaces
   ch=. |:>(3{.&.>ch),&.><&.>(}.@;)&.>(' '&,)&.>&.>3}.&.>ch
   chtit=. 'DATE';'TIME';'SIZE';'NAME'  NB. row titles for "ch"
   whmootdirs=. (ch{~chtit i. <'NAME')e. ,&.>'.';'..'
NB. "SIZE" column has "<DIR>" indicator for directory, size for file.
   whdir=. ((ch{~chtit i. <'SIZE')e. <'<DIR>')*.-.whmootdirs
   addPath&.>(<thisdir,'\'),&.>whdir#ch{~chtit i. <'NAME'
   whfls=. -.whdir+.whmootdirs
   ch=. whfls#"1 ch
   FLNMS=: FLNMS,ch{~chtit i. <'NAME'
   FLPARENT=: FLPARENT,(+/whfls)$thisdn
   FLSZS=: FLSZS,;n2j&.>(ch{~chtit i. <'SIZE')-.&.>','
   FLDTS=: FLDTS,;DateTimeCvt &.>,&.>/' ',&.>ch{~chtit i. 'DATE';'TIME'
   thisdn
)

extract1SubdirList=: 3 : 0
NB.* extract1SubdirList: get first full sub-directory listing out of many.
   'st end'=. y
   ch=. CR-.~(st+i.>:end-st){DIRLSTFL
)

addPath=: 3 : 0
NB.* addPath: put new parent/child index in tree from text of "dir\subdirs..."
   p2a=. <;._1 '\',y    NB. Path to add, e.g. 'C:\top\mid\bottom'
   p2a=. p2a-.a:
   isnew=. 0 [ wh=. _1
   for_nm. p2a do.                 NB. "wh" is parent index of current node...
       if. 0=#wh2=. I. DIRNMS e. nm do. NB. new subdir
           DIRPARENT=: DIRPARENT,wh
           wh=. {.<:#DIRNMS=: DIRNMS,nm NB. will be parent of next node, if any
           isnew=. 1
       else.
           if. 0=#wh2=. wh2#~wh=wh2{DIRPARENT do. NB. Name exists but with
               DIRPARENT=: DIRPARENT,wh           NB.  different parent.
               wh=. {.<:#DIRNMS=: DIRNMS,nm
               isnew=. 1
           else. wh=. {.wh2 end.                  NB. Name exists with
       end.                                       NB.  same parent
   end.
   isnew,wh                              NB. 0 if path was already here
)

Tst0addPath_tests_=: 3 : 0
NB.* Tst0addPath_tests_: test adding path to index vec tree from text.
   coinsert 'parseDir base'
   d0=. DIRNMS=: 'C:';'Aegis';('\'-.~WINDIR}.~WINDIR i. '\');'Web';'printers';'foo';'Web'
   dp0=. DIRPARENT=: _1 0 0 3 4 0 0

   assert. 0 2-:addPath WINDIR     NB. Shouldn't add it again.
   assert. d0-:DIRNMS              NB. Should not have changed
   assert. dp0-:DIRPARENT          NB. Should not have changed

   td=. 'D:\foo\bar'               NB. New path starting from new root
   assert. 1 9-:addPath td         NB. but with same-named sub as existing
   assert. (d1=. DIRNMS)-:d0,<;._1 '\',td
   assert. (dp1=. DIRPARENT)-:_1 0 0 0 3 4 0 0 _1 8 9
   assert. 0 9-:addPath td    NB. Shouldn't add it again.
   assert. d1-:DIRNMS         NB. Should not have changed
   assert. dp1-:DIRPARENT     NB. Should not have changed

   assert. 1 10-:addPath WINDIR,'foo'
   1
)

NB.* mcopyto: text of DOS .BAT file to do multiple copies (in case it's missing).
mcopyto=: 0 : 0
Rem MCopyTo.bat: Multiple COPY TO %1: copy %2, %3, %4, etc.
:START
If %1/==/ goto SHOWHOW
Set tmpnm=%1
:DO1
If %2/==/ goto BYEBYE
Copy %2 %tmpnm% > nul
Shift
Goto DO1
:SHOWHOW
Echo on
Rem  MCOPY target source1 source2...sourceN
Echo off
Goto BYEBYE
:BYEBYE
Set tmpnm=
)

NB.* setTargetDir: Different target dir according to which known machine backed up.
setTargetDir=: 3 : 0
   if. -.nameExists 'TARGDIR' do. TARGDIR=. '' end.
   if. -.nameExists 'TMPDIR' do. TMPDIR=. getTempDir '' end.
   if. 0=#TARGDIR do.             NB. Assign dir prefix based on machine, if known.
       pfx=. KMPFX
       'machnm usernm'=. whoami ''
       pfx=. >pfx{~KNMACH i. <machnm         NB. Prefix based on computer name
       today=. ;4 2 2 lead0s&.>3{.qts ''     NB. today's date as 'yyyymmdd'
       dirnm=: TMPDIR,pfx,today,'\'
   else. dirnm=: endSlash TARGDIR end.
   TMPDIR;dirnm
)
NB. ARGV_z_=: ARGV_z_,<'-jijx'

NB.* runDaily: backup to run every day: save some most recently changed files.
runDaily=: 3 : 0
   TMPDIR=: 'C:\Temp\' [ setGlobalParms ''
   today=. ;4 2 2 lead0s&.>3{.qts ''    NB. today's date as 'yyyymmdd'
   'machnm usernm'=. whoami ''
   if. DEBUGON do.
       logMsg_logger_ 'Running daily backup for machine, user: '
       logMsg_logger_ ' ',machnm,', ',usernm,' at ','.',~showdate ''
   end.
   'TMPDIR dirnm'=. setTargetDir ''
   ASSERR=: '*** Directory "',dirnm,'" does not exist.'
   dodircheck=. 1
   if. 3=#dirnm do.                     NB. if dir is root: 'C:\'
       if. (PATHSEP_j_={:dirnm)*.':'=1{dirnm do. dodircheck=. 0 end.
   end.
   if. dodircheck do.
       if. -.dirExists dirnm do. assert. createdir dirnm end.
   end.
   if. DEBUGON do.
       logMsg_logger_ 'Created directory ',';',~dirnm
       logMsg_logger_ 'starting "getDirFlInfo" at ','...',~showdate ''
   end.

   if. RUNFROMSAVED do. ASSERR=: '*** Error in runDaily at "if. RUNFROMSAVED".'
       TMPDIR=. 'C:\Temp\' NB. [ 4!:55 <'TARGDIR'
       'TMPDIR dirnm'=. setTargetDir ''
       setGlobalParms ''
       TMPDIR runFromSavedVars SZLIM;dirnm
   else. ASSERR=: '*** Error in getDirFlInfo.'
       tmpbkp=. 6!:2 '''FLNMS FLDTS FLSZS FLPARENT DIRNMS DIRDEP''=: getDirFlInfo SRCDIR'
NB.       'FLNMS FLDTS FLSZS FLPARENT DIRNMS DIRDEP'=. PllDirInfoEG 'C:\'
       nm=. 'tmpbkp',today
       ".nm,'=: tmpbkp'
       if. DEBUGON do.
           showtime tmbkp
           logMsg_logger_ 'Saving file and dir info in ',TMPDIR,'...'
       end.
       ASSERR=: '*** Error 0 saving file and dir info.'
       nms=. nm;USUVARS
       ".&.>nms,&.>(<'_base_=: '),&.>nms
       ASSERR=: '*** Error 1 saving file and dir info.'
       xx=. (<TMPDIR) fileVar_WS_&.>nms
       coclass 'parseDir'                   NB. Get back to this namespace after
       ASSERR=: '*** Error 2 saving file and dir info.'
       ".&.>nms,&.>(<'=: '),&.>nms,&.><'_base_'  NB. Ensure local names are same
       coinsert 'base'                      NB. fileVar_WS_ moved us to "base".
       ASSERR=: '*** Error 3 saving file and dir info.'
       if. DEBUGON do.
           logMsg_logger_ 'Save result:'
           logMsg_logger_ >xx
       end.

       if. -.nameExists 'SZLIM' do. SZLIM=. 1e7 end.
       ASSERR=: '*** Error building batch file.'
       'batfl cmds'=: buildBatFl SZLIM;dirnm,~'C:'#~-.':'e.2{.dirnm
       if. DEBUGON do.
           logMsg_logger_ 'About to run batch file at ','...',~showdate ''
       end.
       ASSERR=: '*** Error running batch file ',batfl,'.'
       shell batfl
   end.
   closeLog_logger_ '' [ wait 5
   4!:55 <'ASSERR'       NB. Eliminate global error message if we got this far.
)

NB.* setGlobalParms: set globals according to defaults or command-line overrides.
setGlobalParms=: 3 : 0
   SZLIM=: ''$>1e7 lookupValAfterName ARGV_z_;<'SZLIM'
   if. -.isNum SZLIM do. SZLIM=: ".SZLIM end.
   RUNFROMSAVED=: NYto01 {.>0 lookupValAfterName ARGV_z_;<'RUNFROMSAVED'
   TARGDIR=: ,>lookupValAfterName ARGV_z_;<'TARGDIR'
   SRCDIR=: ,>'C:\' lookupValAfterName ARGV_z_;<'SRCDIR'
   RUNANYDAY=: NYto01,>'N' lookupValAfterName ARGV_z_;<'RUNANYDAY'
   if. -.nameExists 'DEBUGON' do.
       DEBUGON=: NYto01,>0 lookupValAfterName ARGV_z_;<'DEBUGON'
   end.
   ans=. ans,:".&.>ans=. 'SZLIM';'RUNFROMSAVED';'TARGDIR';'RUNANYDAY';'DEBUGON'
)

NB.* NYto01: convert 'N' or 'Y' to 0 or 1, respectively.
NYto01=: 3 : 'if. -.isNum y do. ''Y''=toupper {.y else. y end.'
)

NB.* onlyRuntime: only invoke if not loaded via interactive session.
onlyRuntime=: 3 : 0
   if. (5{.&.>'-jijx';<'-rt') +./ . e. 5&{.&.>tolower&.>ARGV_z_ do.
NB. Only invoke for runtime, not interactive (edit) session.
       if. -.nameExists 'DEBUGON' do. DEBUGON=: 0 end.
       if. -.nameExists 'SESSWDW' do. SESSWDW=: 0 end.
       setGlobalParms ''      NB. Cmd-line size limit, target dir, if present.
       wkdy=. dow 3{.qts ''   NB. Weekday number: 0=Sunday, 6=Saturday
       if. RUNANYDAY+.wkdy e. 1 2 3 4 5 do.      NB. Only Mon-Fri unless
           try. sink=. runDaily '' [ 4!:55 <'ASSERR'   NB.  any day allowed.
           catch. if. nameExists 'ASSERR' do. logMsg_logger_ ASSERR
                  else. msg=. '*** Unspecified error in "runDaily" at '
                        logMsg_logger_ msg,'.',~showdate ''
                  end.
           end.
       end.
       if. -.SESSWDW do.
           closeLog_logger_ '' [ wait 5   NB. 5 secs to read msg.
       end.
   end.
)

NB.* runFromSavedVars: Run backup assuming dir&file vars already saved.
runFromSavedVars=: 3 : 0
   '\amisc\' runFromSavedVars y
:
   bkpargs=. y          NB. 'szlim targ'=. y
   vn=. USUVARS
   (<x,'\'#~'\'~:{:x) unfileVar_WS_&.>vn
   ".&.>(<'parseDir_'),&.>vn,&.>(<'_=. '),&.>vn
   'batfl cmds'=. buildBatFl bkpargs
   shell batfl
   wait 2                                    NB. Give .BAT chance to start.
NB.EG runFromSavedVars 5e6;'C:\Temp\WL20050315\'
)

3 : 0 ''
if. DEBUGON do. (;sepLF&.>'parseDir.ijs:638';<6!:0 '') fappendDHM DBGFL end.
)

coclass 'base'
coinsert 'parseDir'
onlyRuntime ''

locationInfo.ijs

This script sets an arbitrary string to distinguish between the different machines on which I usually work. It has been anonymized here to use only made-up example names.

NB.* locationInfo.ijs: information to distinguish run-time location

NB.* KNMACH: Known machines: network IDs to distinguish different machines I use.
NB.* KMPFX: Known-machine prefixes to de-couple network ID from functionality.
NB.* KNIDS: Known-IDs associated with each machine - good idea?
NB.* whoami: find what machine and user-id I'm on (assume Novell net command).

KNMACH=: (<'\\'),&.>'OldDesktop';'LapHome1';'LapHome2';'WorkDesktop'
NB.* KMPFX: Known-Machine prefixs to use for backup dir.
KMPFX=: 'OD';'LH1';'LH2';'WDsk'
KNIDS=: 'MyOldHomeLogon';'MyHomeLogon';'MyHomeLogon';'MyWorkLogon'
NB. Home: old desktop, laptops;  Work Main Machine (desk),

NB.* whoami: find what machine and user-id I'm on (assume Novell net command).
whoami=: 3 : 0
   sess=. spawn 'net config workstation'
   sess=. dsp&.><;._1 LF,sess-.CR            NB. Vec of lines
   sess=. ><;._1&.> ' ',&.>,sess             NB. Mat of words
   strs=. 'COMPUTER';<'USER'                 NB. Strings to key on
   wh=. (<toupper&.>0{"1 sess)e.&.> <&.>strs NB. Which lines start with strings
   wh=. ;b2i&.>(<(toupper&.>1{"1 sess)e. <'NAME')*.&.>wh
   wh{2{"1 sess
NB.EG 'machine userid'=. whoami ''
)

Parallel Directory-Parsing Task Template

This is the template modified by the main routine and run as a distinct task for each top-level directory.

NB.* parallelParseDir.ijs: gather directory info in parallel.

usuUse=: 0 : 0
   1!:44 dd=. 'C:\amisc\J\Parallel\ExampleProblems\DirInfo\'
   load 'parallelParseDir.ijs'
   6!:2 '''FLNMS FLDTS FLSZS FLPARENT DIRNMS DIRDEP''=: PllDirInfoEG ''C:\'''
)

PllDirInfoEG=: 3 : 0
NB.* PllDirInfoEG: gather dirs' info accumulated in parallel.
   'tmpd srcd flnms fldts flszs'=. pllParseDir y=. endSlash y
   flszs=. ;flszs [ fldts=. cvtTS21Num&>fldts
   flparent=. 0$~#flszs
   dirdep=. _1,0$~#srcd [ dirnms=. y;srcd
   vnms=. <;._1 ' FLNMS FLDTS FLSZS FLPARENT DIRNMS DIRDEP'
   for_ii. i. #tmpd do.
       vals=. (3!:2)&.>fread&.>(<endSlash >ii{tmpd),&.>vnms,&.><'.DAT'
       'flnms fldts flszs'=. (flnms;fldts;<flszs),&.>3{.vals
       flparent=. flparent,(#dirnms)+>3{vals
       dirnms=. dirnms,>4{vals
       ddep=. (#dirnms)|dirnms i. (]{.~PATHSEP_j_ i:~])&.>dirnms
       dirdep=. (ddep=i.#ddep)}ddep,:_1
   end.
   cleanupTempDirs 'PllDTmp'
   flnms;fldts;flszs;flparent;dirnms;<dirdep
NB.EG 'flnms fldts flszs flparent dirnms dirdep'=. PllDirInfoEG 'C:\'
)

NB.* endSlash: ensure path has ending slash.
endSlash=: 13 : 'y,PATHSEP_j_#~PATHSEP_j_~:{:y'
SUBTASK=: <jpath '~Code/pllPDSub.ijs'
dq=: '"','"' ,~]                        NB.* dq: put double-quotes around y.

pllParseDir=: 3 : 0
NB.* pllParseDir: launch sub-tasks to parse each sub-directory under y.
   if. 0=#y do. y=. 'C:\' end.
   srcDirs=. jd dir '*',~y=. endSlash y
   'flnms fldts flszs'=. <"1|:0 1 2{"1 jfi dir '*',~y
   cleanupTempDirs 'PllDTmp'
   tmpDirs=. (<'C:\Temp\PLLDTmp'),&.>":&.>i.#srcDirs   NB. Don't end w/slash->escapes "
   args=. SUBTASK,&.>(<' SRCDIR '),&.>dq&.>(<y),&.>srcDirs  NB. Build command
   args=. args,&.>(<' RESULTDIR '),&.>dq&.>tmpDirs          NB.  for each dir.
   if. -.fexist BINPATH,'\PllPD.exe' do. (BINPATH,'\J.exe') fcopy BINPATH,'\PllPD.exe' end.
   fork&>(<dq BINPATH,'\PllPD.exe'),&.>(<' -jijx '),&.>args
   checkIfDonePPD 'PllPD'
   tmpDirs;srcDirs;flnms;fldts;<flszs
NB.EG 'tmpd srcd flnms fldts flszs'=. pllParseDir 'C:\'
)

checkIfDonePPD=: 3 : 'while. 5<LF+/ . =shell ''pslist|egrep '',y do. wait 1 end.'

cleanupTempDirs=: 3 : 0
   tmpdirs=. (<'C:\Temp\'),&.>jd dir 'C:\Temp\',y,'*'
   shell&>(<'echo Y|del '),&.>tmpdirs,&.><'\*'
   1[shell&>(<'rmdir '),&.>tmpdirs
)

0 : 0
NB.* Only invoke this if not loaded via interactive session.
   if. (5{.&.>'-jijx';<'-rt') +./ . e. 5&{.&.>tolower&.>ARGV_z_ do.
       'FLNMS FLDTS FLSZS FLPARENT DIRNMS DIRDEP'=. PllDirInfoEG 'C:\'
       (<'\Temp\') fileVar_WS_&.>'FLNMS';'FLDTS';'FLSZS';'FLPARENT';'DIRNMS';'DIRDEP'
       'batfl cmds'=. buildBatFl_parseDir_ 12e6;'C:\Temp\Recent\'
       shell batfl
   end.
)

Mutual Exclusion

Here is the script implementing mutex by means of a semaphore file using the file-locking primitives in J. There are two scripts: the basic routines for mutex and a set of test cases to validate the correctness of these routines.

NB.* mutex.ijs: implement mutual exclusion via file locking.

coclass 'mutex'
require 'task'      NB. Only for "spawn" in "whoami" - not necessary.

fopen=: [:1!:21<    NB. Open file named by y.
fclose=: [:1!:22<   NB. Close file name or number y.
flock=: 1!:31       NB. File number, index, length of region to lock
funlock=: 1!:32     NB. File number, index, length of region to unlock
whlocks=: 1!:30     NB. Which locks - 3-cols: file #, index, and length
whfiles=: 1!:20     NB. Which files - 2-cols: file #; name
getpid=: 2!:6       NB. Current process ID
qts=: 6!:0          NB. Timestamp -integers: Y M D h m s.s (millisec?)

LOCKFL=: 'Lock.fl'  NB. Central hold file
LFINFO=: (i.0);''   NB. Hold file details while holding

NB.* getMyInfo: nice to have info identifying who has the lock and when.
getMyInfo=: 3 : '100{.(;'' '',~&.>whoami''''),":(getpid,qts)'''''

NB.* getHold: put hold on lock file.
getHold=: 3 : 0
   fid=. fopen LOCKFL [ myinfo=. getMyInfo ''
   if. -. fid e. ;0{"1 whlocks '' do.             NB. If we don't have lock,
       while. -.flock fid,0,100 do. wait 1 end.   NB.  keep waiting until we
       myinfo fwrix_fldir_ fid;0                  NB.  get it; put ID info in
       LFINFO=: fid (]{~[i.~[:;0{"1]) whfiles ''  NB.  file & update global.
   end.
   fid;myinfo
NB.EG 'fid myinfo'=. getHold ''  NB. File ID, my ID info.
)

NB. funlock (>0{LFINFO) (]{~[i.~[:;0{"1]) whlocks ''
releaseHold=: 3 : 0
   if. 0~:#>0{LFINFO do. 0*./ . =#&>LFINFO=: (i.0);'' [ fclose >0{LFINFO end.
)

NB.* getMUP: get Machine name, User ID, Process ID from my info string.
getMUP=: 13 : '3{.<;._1 '' '',y'

NB.* dsp: Despace - remove leading, trailing and redundant spaces.
dsp=: [:(#~(+.(1:|.(></\)))@(' '&~:))"1 (#~([:(+./\*. +./\.)' '&~:))"1

NB.* whoami: get machine and user ID - Windows only.
whoami=: 3 : 0
   assert. 6-:9!:12 ''                       NB. Fail if not Windows
   sess=. spawn 'net config workstation'
   sess=. dsp&.><;._1 LF,sess-.CR            NB. Vec of lines
   sess=. ><;._1&.> ' ',&.>,sess             NB. Mat of words
   strs=. 'COMPUTER';<'USER'                 NB. Strings to key on
   wh=. (<toupper&.>0{"1 sess)e.&.> <&.>strs NB. Which lines start w/ strings?
   wh=. ;I.&.>(<(toupper&.>1{"1 sess)e. <'NAME')*.&.>wh
   wh{2{"1 sess
NB.EG 'machine userid'=. whoami ''
)

Mutual Exclusion Test Cases

Here are routines to test various aspects of the mutex implementation.

NB.* mutex_TCs.ijs: test cases for mutex.ijs.

coclass 'mutexTest'
require '~Code/mutex.ijs'
coinsert 'mutex'

checkIfSelfLocked=: 3 : '(getMUP getMyInfo '''')-:getMUP y'
endSlash=: ],'\'-.{:
wait=: 6!:3

TC0=: 3 : 0
   'fid myi'=. getHold ''
   assert. -. 0*./ . =#&>LFINFO
   releaseHold ''
   assert.    0*./ . =#&>LFINFO
)

TC1=: 3 : 0
   'fid myi'=. getHold ''
   assert. checkIfSelfLocked fread fid
   releaseHold ''
)

TC2=: 3 : 0
   'fid myi'=. getHold ''
   assert. checkIfSelfLocked myi
   releaseHold ''
)

TC3=: 3 : 0
   'fid0 myi0'=. getHold ''
   'fid1 myi1'=. getHold ''
   assert. fid0-:fid1
   assert. checkIfSelfLocked myi0
   assert. checkIfSelfLocked myi1
   releaseHold ''
)

TC4=: 3 : 0
   'fid myi'=. getHold ''
   assert. fid=>0{LFINFO
   assert. fid e. ;whlocks ''
   assert. 3=#fid (]{~[i.~[:;0{"1]) whlocks ''
   assert. LFINFO-:fid (]{~[i.~[:;0{"1]) whfiles ''
   releaseHold ''
)

NB.* holdAndWait: hold file for a random time, then release.
holdAndWait=: 3 : 0
   tm=. tm,qts'' [ 'fid myi'=. getHold '' [ tm=. ,:qts ''
   rc=. *./checkIfSelfLocked &> myi;fread fid
   rc=. rc,wait >:?y
   releaseHold ''
   rc;tm       NB. If self-held, holding time; when asked and got hold.
)

NB.* holdsAndReleases: do number of holds and releases, waiting between each.
holdsAndReleases=: 3 : 0
   'ni wl mhl'=. y  NB. Number of iterations, wait length, max hold length.
   rc=. 0 2$0 2;2 7$2
   while. _1<ni=. <:ni do. wait wl [ rc=. rc,holdAndWait mhl end.
   rc
)

NB.* startHRs: start holders and releasers as separate processes.
startHRs=: 3 : 0
   if. 0=#y do.
       args=. |.(10+|.i.10),. 2 2,2 3,3 2,3 3,2 4,4 2,4 4,2 5,5 2,:5 5
       dd=. 'C:\amisc\J\Parallel\ExampleProblems\Holds\'
   else. 'dd args'=. y end.
   if. 0=#(1!:0)@<(]}.~[:-'\'={:) dd do.     NB. Create dir if doesn't exist.
       try. 1!:5 <dd catch. assert. 0 end. end.
   start1HR"1 dd;"1 args
)

start1HR=: 3 : 0
   'rundir arg'=. y
   1!:44 rundir=. endSlash rundir [ svdir=. 1!:43 ''   NB. Run in target dir.
   basis=. fread jpath '~Code/mutex_TCs.ijs'      NB. Get base code.
   rndnm=. 8 (]{~[:?[$[:#]) 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
NB. Obfuscate rand name place-holder to avoid replacement here.
   rplargs=. '{',&.>('RN';'arg';'rundir'),&.>'}'
   addon=. (,rplargs,.rndnm;(":arg);rundir) stringreplace addon
   if. fexist hrfl=. 'HR_',rndnm,'.ijs' do. ferase hrfl end.
   (CR-.~basis,addon) fwrite hrfl                 NB. Create unique HR file.
   exe=. (<'"',(jpath '~bin'),'\j.exe" -jijx "'),&.><endSlash rundir
   fork&.>exe,&.>(<hrfl),&.>'"'                   NB. Run HR file.
   1!:44 svdir
)

addon=: 0 : 0
varnm=. 'rctm{RN}'
(varnm)=: holdsAndReleases {arg}
(varnm)=: ('{RN}';(getpid ''),qts''),".varnm
(3!:1 (".varnm)) 1!:2 <'{rundir}',varnm,'.dat'
2!:55 ''
)

Checking Results of Mutual Exclusion Tests

The results of the startHRs tests need subsequently to be analyzed to ensure that no holds or releases overlap between separate processes, as shown here:

NB.* checkMutexOverlap: examine mutex hold and release times to find overlap.

NB.* Regularize vec of mats to 3-cols: time, ID, "H" (hold) or "R" (release),
NB. ordered by timestamp.
regMatz=: [:(] ,. ('H';'R')$~#) ([:<"1 [:,/[:>[:,/1{"1}.),.(<0 0){]
checkHRSeqc=: (2 {"1 ]) -: ('H';'R') $~ #
checkIDSeqc=: [:*./_2=/\1{"1]
egUse=: 0 : 0
   dd=. 'C:\amisc\J\Parallel\ExampleProblems\HoldsWL2M100713\'
   fls=. 0{"1 dir dd,'*.dat'
   hldtms=. 3!:2&.>fread&.>(<dd),&.>fls
   hh=. >,&.>/regMatz&.>hldtms
   rc=. 0
   assert. checkHRSeqc hh
   assert. checkIDSeqc hh
   rc=. 1
)