User:Devon McCormick/ParallelizedJCodeExamples
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 )