Running only one instance of a batch file ?

Sep 11, 2012
84
1
#1
Anyone have a good tip on how to make sure a particular TCC batch file will not run more than one instance at a time ?

I would prefer to have a function like %@isrunning[%0]

It is quite easy to do using flag files, but using PIDs would be more elegant - other ideas ?

Thanks for any tip!

-stein
 
#2
Anyone have a good tip on how to make sure a particular TCC batch file will not run more than one instance at a time ?

I would prefer to have a function like %@isrunning[%0]

It is quite easy to do using flag files, but using PIDs would be more elegant - other ideas ?

Thanks for any tip!

-stein
Hmmm! Nice idea. I can think of a few things, none particularly elegant.

You could use flag registry entries (instead of files). If you use global aliases (or functions) you could use a dummy "flag" alias (or function). My 4UTILS plugin has GSET and @GV[] for setting/reading "global" variables (they're actually in the registry, available to all). I suppose a future TCC might have a data segment, shared by all instances, and a mechanism for putting yet another class of variable there (that could be done in a plugin as long as all instances used the plugin) but I don't think the demand for that is great. You could put a flag in the /S(ystem) or /U(ser) environment, or better yet, in the /V(olatile) environment. I'm confident there are more options.

With any such mechanism, one problem will be getting rid of the flag when the batch file terminates abnormally
 
#3
I've had some luck implementing such things via a lock file, registry entry, or environment variable. Any of these can work, depending on how much communication you need between processes. When the batch file launches, it needs to check to see if there is an existing lock, and if not, set a lock with it's PID, wait 1 second and check if the PID still matches, if so, run, and delete the lock.

If there is already a lock in place, check to see if the PID still has a running process, if so, terminate. If the process has abandoned its lock file, assume that its orphaned and set a new lock. For bonus points, take into account that PIDs can be reused.

Why set the lock and wait 1 second? Well if you happen to start multiple instances of the batch file at exactly the same time, they'll both do some sort of "IF NOT ISFILE" or whatever, and then create locks that overlap. Using a file operation that blocks or locks the file so that subsequent events fail can help, especially since the OS will clean up the read-locks on the .lock files for you, but every once in a while, this still fails for reasons that aren't entirely clear.

I prefer using a file over the registry because you can dump errors or other "stuff" into the lock file and when another instance runs and finds a previous lock file to be abandoned, the details can be useful for troubleshooting.

There is, of course, some non-zero overhead here, no matter what.
 
#4
Here's an easy, nifty one:
Code:
set h=%@fileopen[%0,a,t]
Another instance trying to run the BTM will produce, for example,
Code:
TCC: V:\avtemp.btm [0]  Batch file missing "V:\avtemp.btm"
Do not use @fileopen[] with the "W(rite)" option instead of "A(ppend)". That will wipe out the contents of the file!!!!
 

samintz

Scott Mintz
May 20, 2008
1,295
11
Solon, OH, USA
#7
Why not just use the volatile registry? It's easy to use and goes away after a power cycle. The downside is it is tied to the current user: HKCU\Volatile Environment.

Code:
iff %@execstr[set /v %_BATCHNAME].==. then 
  set /v %_BATCHNAME=1
  rem do stuff
  unset /v %_BATCHNAME 
else
  echo %_BATCHNAME is already running
endiff
 

samintz

Scott Mintz
May 20, 2008
1,295
11
Solon, OH, USA
#8
Another option would be to use my EVENT plugin. As it is currently written, events are created in the unsignaled state. So you would need to create and signal it prior to running your scripts.
Your initial setup would be:
Code:
EventSignal %@EventCreate[MyBatchLock]
Then your scripts would do this:
Code:
set myevt=%@EventCreate[MyBatchLock]
EventWait %myevt /t0
iff %_? == -1 then
  echo %_BATCHNAME is currently running
  quit
endiff

rem do stuff

rem Reset the event for next time
EventSignal %myevt
 
Nov 2, 2008
193
1
#10
I used to use flag files in the directory, which were created and released whenever the program the batch launched was running.

For example, i was running UBASIC, and capturing its output. It is important two programs don't run because they use the same output. The program would first check for a file before loading, and if the file was loaded, it would not start.

You could manually over-ride this.
 
Sep 11, 2012
84
1
#11
Thanks to you all for the suggestions.

I choosed to use the DESCRIBE tip, using the following:

ALIAS
----------------
1call=call %1 & describe %1 /D"Finished - %_datetime"

test-instance.btm
-------------------------
@echo off
set status=%@instr[0,8,%@descript[%0]]
echo Status: %status%
iff %status% eq Running then
echo Already running - %@descript[%0]
else
echo Hey, I am really on my own, lets continue....
describe %0 /D"Running - started %_datetime"
delay 10
defer describe %0 /D"Finished normally %_datetime"
endiff

test1.cmd
----------------
@echo off
1call e:\batch\test-instance.btm

test2.cmd
----------------
@echo off
1call e:\batch\test-instance.btm

Running test1.cmd in one TCC session and test2.cmd in another did what I needed

I plan to enhance it so that the PID and a maximum number of seconds to run is included in the description and so make it possible to terminate a hung process.

Other suggestions are still welcome though.

Thanks again
Stein
 
Apr 24, 2015
1
0
#12
Hi Stein Oiestad!

Meanwhile, your solution should be working.You asked your question quite a few days ago, but here I suggest to solve it with a "real" function "like %@isrunning[%0]"

My solution is not to ask for "%0", but for a footprint left by your own script. There are a few considerations beforehand:

The script should be recognized even if it is running with a different name, maybe started by a different process, and maybe in an older version.

So, we don`t rely on the filename "%0", but on a hard coded signature written to the running program`s TITLE.
Let us assume the script internally is named "SOV123: Stein O Version 1.2.3a".
One of the script`s lines looks like

TITLE SOV123: Stein O. Version 1.2.3a started %_date %_time %@crc[%_batchname]

Next thing is to find out whether there is another instance of SOV123 already running.

We can use a combination of TASKLIST and FFIND:

FUNCTION IsRUNNING=`%@eval[%@word["* ",0,^
%@execStr[tasklist | ^
ffind /UHKL /V /E"%@crc32[^
%@lfn[%_batchname]]"]]]`

FUNCTION IsRUNNING2=`%@eval[%@word["* ",0,^
%@execStr[tasklist | ^
ffind /UHKL /V /E"%fingerprint%"]]]`


( a hint: using /E"" instead of /T"" doesn`t harm...)

I have written a small demo around this function. It is tested with XP (mostly failed, donno why, maybe because of VM?) and 8.1 (OK),TCC Demo versions 12 and 17.
TCC/LE does _NOT_ work, of course.

Code:
[FONT=Courier New]@ECHO OFF
:: (tiny demo)
:: set a unique fingerprint
set fingerprint=SOV123
title %fingerprint %@crc32[%@lfn[%_batchname]]

:: this function identifies the EXACT copy of
:: the batch file. It does not need any parameter!

FUNCTION IsRUNNING=`%@eval[%@word["* ",0,^
%@execStr[tasklist | ^
ffind /UHKL /V /E"%@crc32[^
%@lfn[%_batchname]]"]]]`
::
:: some lines of code to test the function
::
:: just for DEMO purposes... set it to your needs, or
:: do it hard-coded as "1" in the IFF-clause below
set MAX=4
:: 
set Instances=%@IsRUNNING[%0]
::
title %0:instance=%instances% %@crc32[%@lfn[%_batchname]]
echo instance=%instances%; allowed: %max%
pause look at the TITLE! (press a key)

IFF %instances% gt %MAX% then
SETDOS /E\
msgBox OK "FORBIDDEN!" more than %max%\n instances NOT allowed!
exit
ENDIFF
echo running normally: %_batchname
echo  Trying to start twice
start /PGM "%_batchname" & pause Any key to exit....
exit[/FONT]
[FONT=Arial]
:: Have a nice day!
[/FONT]
[FONT=Courier New][FONT=Arial]:: Tom[/FONT][/FONT]
 
#13
Nice work, tom_d!

I'm using TCC/LE so I had to take another route:

Code:
@echo off
setlocal

:: Backup Windows Title
   set ORIGINAL_TITLE=%_WINTITLE


:: Find task with .LOCKED title
   tasklist /FI "WINDOWTITLE eq %@FULL[%0].LOCKED"| ffind /T"PID">nul && set ISRUNNING=1
   if %ISRUNNING=1 QUIT    :: Or whatever you want to do ...


:: New Window title (make it .LOCKED)
   TITLE %@FULL[%0].LOCKED


rem Your code here ....
pause

:: Restore original Window Title (well.. sort of; at least the LOCK is gone)
   TITLE %ORIGINAL_TITLE

Tasklist has some unexpected behaviour.
First of all, it doesn't give you an %ERRORLEVEL% (that's why the ffind command)
Second: Tasklist doesn't find it's own (instance of) Window title. So you could change the Window title right away in .LOCKED, without Tasklist finding it(self). Strange...


EDIT - Slight improvements:

  1. To get the original window title back, use
    Code:
    set ORIGINAL_TITLE=%@replace[ - %0,,%_WINTITLE]
  2. If you want also ISRUNNING=0 returned when applicable (if you want to make it a function:
    Code:
    tasklist /FI "WINDOWTITLE eq %_batchname.LOCKED"| find /i"PID">nul & set ISRUNNING=%@eval[!%_?]
    NB: tasklist .. | ffind .. & echo %? %_? didn't give the errorlevel off FFIND, so I had to use FIND
  3. Better use %_batchname instead of %@FULL[%0] ; dummy.btm and dummy give you different %@FULL[%0], but the same %_batchname
 
Last edited:
#14
Man, a lot of new commands and functions since I last used 4NT/TCC (must be 15 years ago ...)
This can now be done much more straightforward:

Code:
@echo off
setlocal

:: Backup Windows Title (without current script)
   set ORIGINAL_TITLE=%@replace[ - %0,,%_WINTITLE]

:: Find task with .LOCKED title; quit if found
   IF iswindow "%_batchname.LOCKED" quit  :: Or whatever you want to do ...

:: New Window title (make it .LOCKED)
   TITLE %_batchname.LOCKED

rem Your code here ....
pause

:: Restore original Window Title
   TITLE %ORIGINAL_TITLE

third time's a charm :-)