Limit number of running applications with NewNamedSemaphore example

To limit the number of running application, this code can be used. In this example the limit will by set by a value set in the database, the limiting has to be done after open files.

Please not that some templates and derived WindowManager classes don’t like being terminated by RETURN LEVEL:Notify from the Init() method.

Semaphore names must be unique, they share a system wide namespace.

Inside application Main (Frame) procedure:

#AT Local Data \ Generated Declarations

AppLock             &ISemaphore
LOC:ApplicationLimit LONG

#AT Local Objects \ Abc Objects \ Window Manager \ Init () DATA

LockErr             LONG

#AT Local Objects \ Abc Objects \ Window Manager \ Init () CODE, priority 7800

    LOC:ApplicationLimit = 3 ! Or fetch from database
    AppLock &= NewNamedSemaphore('MyApp_MaximumApplications', LOC:ApplicationLimit, LOC:ApplicationLimit, LockErr)
    IF AppLock &= NULL THEN
      MESSAGE('Error NULL object preparing application limit')
    END
    IF LockErr = ERROR_ALREADY_EXISTS  THEN ! 183=ERROR_ALREADY_EXISTS
      ! Use existing semaphore  
    ELSIF LockErr > WAIT:OK THEN
      MESSAGE('Error ' & LockErr & ' preparing application limit')
      AppLock.Kill()
      AppLock &= NULL
    ELSE
      AppLock.Release(LOC:ApplicationLimit) ! Make 3 available
    END
 
    IF AppLock.TryWait(100) = 1 THEN ! Reserve one
      AppLock.Kill()
      AppLock &= NULL
      MESSAGE('Maximum number of applications has been reached.|Close another application first.')
      RETURN LEVEL:Notify
    END

#AT Local Objects \ Abc Objects \ Window Manager \ Kill () CODE, priority 5050

IF NOT AppLock &= NULL THEN ! Release one
  AppLock.Release()
  AppLock.Kill()
  AppLock &= NULL
END
1 Like

I had posted below comments in Skype chat about naming your Semaphore.

  1. Named Kernel Objects are in in scope across all Processes so your name must be unique, don’t use “MySemaphore”.
  2. Include a GUID would be good to make it very unique. The editor has a feature to insert one under Right-click + Insert + GUID
  3. All Object types (Mutex, Semaphone, Event…) share the same namespace so maybe append “_Semaphore”. If you reuse the same Name for a different type Object you’ll get Error 6 ERROR_INVALID_HANDLE which is not much help.
  4. Using PATH() can make object names unique per install BUT Object Names cannot contain \ backslash so change to _ Underscore
  5. Maximum length is same as a file name 260 bytes
  6. There are special names like "Global" that can be used across all sessions on WTS.
  7. You should read MSDN in CreateSemaphore and OpenSemaphore.
  8. Read “Windows via C/C++, fifth edition by Jeffrey Richter” which covers all Kernel Objects very well. You can find a free PDF download.

Your name 'MyApp_MaximumApplications' should include a GUID so it is very unique. You could also include the PATH() to allow your EXE running in different folders to be started. As I noted in #4 the Backslash must be changed to another character.

For the Semaphore name you could use COMMAND(‘0’) that returns the Full Path and EXE Name. That way you do not need to decide a name. I would still add a GUID. That would have a weakness that the user could Copy the EXE and run that for more instances.

I made some changes and pasted below. This is not tested.

I would have the First program that created the Semaphore not release all 3 i.e. keep 1 for itself. Also if the TryWait() is anything other than Wait:TimeOut (e.g. WAIT:NoHandle WAIT:Failed WAIT:Abandoned) that would indicate bugs so I show the WaitResult in the message. There were a few places a RETURN was needed.

LOC:ApplicationLimit = 3 ! Or fetch from database
LOC:NameSemphore='MyApp_MaximumApplications_0567D94A-9BDE-4110-A73D-F722E2E9EB4A'
!TODO Clean Name to remove any \ Backslash
AppLock &= NewNamedSemaphore(LOC:NameSemphore,     |
                             LOC:ApplicationLimit, |     !Initial Locked Count, take all
                             LOC:ApplicationLimit, |     !Maximum Lock Count
                             LockErr)
IF AppLock &= NULL THEN
   MESSAGE('NULL Semaphore preparing application limit ' & |
           '|Error '& LockErr &'|Name ' &LOC:NameSemphore ,Command('0'))  !CB Show LockErr and Name
   AppLock.Kill()
   RETURN LEVEL:Notify           !CB Return or Crash?
END
CASE LockErr
OF 0 !This EXE Created the Semaphore it must be First
     !Do any ONE TIME initialization needed before opening the door to others
     !e.g. file conversion. Maybe delay the Release until First opens the Frame.
     AppLock.Release(LOC:ApplicationLimit - 1) ! Make 2 available, keep 1 

OF ERROR_ALREADY_EXISTS   ! 183=ERROR_ALREADY_EXISTS
  ! Use existing semaphore. Lock Initial count and Max were ignored.
   WaitResult = AppLock.TryWait(100)    ! Reserve one, wait up to 0.1 seconds 
   IF WaitResult <> WAIT:OK THEN        ! Normally fail with WAIT:TIMEOUT
      AppLock.Kill()
      AppLock &= NULL
      MESSAGE('Maximum number of applications has been reached.' & |
             '|Close another application first.' & |
             CHOOSE(WaitResult=WAIT:TIMEOUT,'','||TryWait unexpected result: ' & WaitResult &'|Name ' &LOC:NameSemphore ) ,|  !Cb show error
             'Name of Program')
      RETURN LEVEL:Notify
   END          
ELSE !LockErr <> 0 and <>183 
 !?? Doubt you'll ever get here because a failure will probably leave AppLock = Null ??
   MESSAGE('Unknown NewNamedSemaphore Error ' & LockErr & ' preparing application limit' & |
          '||Name ' &LOC:NameSemphore,Command('0'))
   AppLock.Kill()
   AppLock &= NULL
   RETURN LEVEL:Notify
END

I would show the LockErr number in messages as well as the Name for unexpected fails.