Script recipe of the week: multitasking and syncronization in batch files

Multitasking is an easy call. All you have to do is to start a separate process using the start command. Unless you specify the /WAIT option (which would cause a syncrhonous execution) the START command will simply start the other process, and return immediately to you. Here is an example:

   start dir /s

But what about synchronization? Here, an interesting but little known command-line utility is the WAITFOR command. This can be used to synchronize the execution between two scripts. The command is easy to use. In a CMD script, we can wait for a certain (named) event, say YourEvent1:

   waitfor YourEvent1

Then, in a separate CMD script you can signal this event by calling again WAITFOR but with the /SI flag:

   waitfor /SI YourEvent1

It is even possible to specify a certain timeout (with the /Toption), or even signal on a different system. I even use WAITFOR as a cheap trick to sleep a number of seconds in a batch file. For example, the command below waits for ten seconds:

   Y:\util>waitfor /T 10 NonExistentEvent
ERROR: Timed out waiting for 'NonExistentEvent'.

In some form, WAITFOR is the equivalent of the Windows Events (the CreateEvent/SetEvent/PulseEvent API). But in reality, waitfor doesn't use events. It internally uses a mailslot device instead (since events cannot be set/reset over the network, but mailslot devices can). For example, if we run "waitfor SetupReady" command we can see the mailslot device being present with that name:

The WAITFOR command can be used in more elaborate ways. For example, you can implement a simple CMD batch file that serializes the execution of two separate commands. Here is the code:

@setlocal

@echo.
@echo Create the lock path if missing...
@echo.
set LOCK_PATH=%systemdrive%\_lock\
if not exist %LOCK_PATH% md %LOCK_PATH%
set LOCK_FILE=%LOCK_PATH%\lock_file.cmd

@if "%1"=="/force_unlock" @goto :UNLOCK

@echo.
@echo Get a unique ID ...
@echo.

@set UNIQUE_ID=%date%-%TIME%-%RANDOM%
@echo.
@echo Unique ID: %UNIQUE_ID%
@echo.
goto :ATTEMPT_ACQUIRE

:RETRY
@echo.
@echo wait for the unlock signal
@echo.
waitfor LOCKSIGNAL

@echo.
@echo Unlock signal detected.
@echo Wait for a random number of seconds to avoid bursts ...
@echo.
@set /A random_seconds=%random%/3000
waitfor /T %random_seconds% NONEXISTENTSIGNAL

:ATTEMPT_ACQUIRE

@echo Attempt to grab the lock ...
if exist %lock_file% goto :RETRY
echo set PERSISTED_UNIQUE_ID=%UNIQUE_ID%> %lock_file%

@echo.
@echo Check to see if we succcesfully grabbed the lock
@echo.

call %lock_file%
if "%PERSISTED_UNIQUE_ID%"=="%UNIQUE_ID%" goto :ACQUIRE_LOCK
goto :RETRY

:ACQUIRE_LOCK

@echo.
@echo Execute the command: %1 %2 %3 %4 %5 %6 %7 %8 %9
@echo.
call %1 %2 %3 %4 %5 %6 %7 %8 %9

@echo.
@echo Signal all other CMDs that we are done
@echo.

:UNLOCK

del /f %lock_file%
waitfor /SI LOCKSIGNAL

In the example above, I used WAITFOR to communicate between several processes on the same machine. Along with the WAITFOR command, I also used a global store (which in this case is a file) but you can use also a registry key or something similar. To use this script, just invoke it with the command to be serialized as parameter. For example, the commands below will be serialized even if they are executed in separate CMD windows (or even different logon sessions):

(command window 1)
serialize.cmd dir /s z:\

(command window 2)
serialize.cmd dir /s y:\

To conclude, although a little cumbersome, you can use WAITFOR to implement all sorts of synchronization options using these commands in a CMD script. If you know other CMD tricks for inter-process syncronization, I would love to learn them...