Sign in to follow this  
Followers 0
xwing1978

_Singleton and a Server Class OS (Win2k3)

16 posts in this topic

Windows Server 2003 allows multiple people to remotely connect to the server at the same time.

There's a script in the Startup folder that uses _Singleton (see example).

When multiple people log in to the server the script is run multiple times.

Is this as designed or a bug, I only want the script to run once and only once?

The _Singleton function works like a charm on my XP and Vista machines.

I attached a sample script.

I saw this post (link) and there was a brief reference to a server os but no other comments or a resolution to the issue.

_Singleton.au3

Share this post


Link to post
Share on other sites



Windows Server 2003 allows multiple people to remotely connect to the server at the same time.

So does "workstation", or XP.

There's a script in the Startup folder that uses _Singleton (see example).

When multiple people log in to the server the script is run multiple times.

Is this as designed or a bug, I only want the script to run once and only once?

The _Singleton function works like a charm on my XP and Vista machines.

I attached a sample script.

I saw this post (link) and there was a brief reference to a server os but no other comments or a resolution to the issue.

I don't think server OS is relevant. Unless they are admins, the users probably can't see the instances started by other users in their own context.

:)


Valuater's AutoIt 1-2-3, Class... Is now in Session!For those who want somebody to write the script for them: RentACoder"Any technology distinguishable from magic is insufficiently advanced." -- Geek's corollary to Clarke's law

Share this post


Link to post
Share on other sites

#3 ·  Posted (edited)

So does "workstation", or XP.

Only one user can use a Windows XP Professional-based computer at a time, either by logging on to the console or by connecting remotely. (link)

I don't think server OS is relevant. Unless they are admins, the users probably can't see the instances started by other users in their own context.

To view the processes open by other users, at least on XP, Vista, and Server 2003, simply open task manager -> Click the processes tab -> and check "Show processes from all users" (in my case all the users are administrators)

:)

Edited by xwing1978

Share this post


Link to post
Share on other sites

Only one user can use a Windows XP Professional-based computer at a time, either by logging on to the console or by connecting remotely. (link)

You need a copy of PSTools, or SCHEDTASKS.exe, etc... just because only one can get on the Console at time, doesn't mean others can't run remote processes on your workstation. Remove the firewalls and put it on a direct internet connection for a while and you will be AMAZED at the number of remote processes that can be initiated by other users... :)

To view the processes open by other users, at least on XP, Vista, and Server 2003, simply open task manager -> Click the processes tab -> and check "Show processes from all users" (in my case all the users are administrators)

:blink:

I shouldn't have gone there anyway. The _Singleton() function doesn't check process names, it creates a named mutex. If the script tries to run again, re-creation of the mutex with the same name fails and triggers the response called for by $iflag. Are you running it with $iflag = 1? If so, then _Singleton only returns 0 and sets @error rather than exiting the scirpt. Using a variable instance name in the call to _Singleton() would also break it because the various names of the mutexes created will not conflict with each other.

Post the line from you script where you call the _Singleton function, and enough code to see how you generate the instance name (if it's not a static string).

The user context issue becomes: If a user owned process creates a named mutex, is it visible to other users? And I don't know the answer (yet).

However your script is running, if you know for a fact that other users can see each other's processes, and that those will not be renamed, then you could code your own version of _Singleton just using ProcessExists().

:P


Valuater's AutoIt 1-2-3, Class... Is now in Session!For those who want somebody to write the script for them: RentACoder"Any technology distinguishable from magic is insufficiently advanced." -- Geek's corollary to Clarke's law

Share this post


Link to post
Share on other sites

This isn't a bug, it's just a quirk.

http://msdn2.microsoft.com/en-us/library/aa382954.aspx

You should be able to prepend "Global\" to the name of the Mutex to create it in the global namespace at which point it should work.

Problem with that is the third line of code in the _Singleton() UDF strips the backslash:

; #FUNCTION# ====================================================================================================================
; Name...........: _Singleton
; Description ...: Check if no other occurence is running
; Syntax.........: _Singleton($sOccurenceName[, $iFlag = 0])
; Parameters ....: $sOccurenceName - String to identify the occurrence of the script
;                  $iFlag          - Action if @error
;                  |0 - Exit if occurrence already exists
;                  |1 - Return if occurrence already exists
; Return values .: Success      - 1
;                  Failure      - 0
; Author ........: Valik
; Modified.......:
; Remarks .......:
; Related .......:
; Link ..........;
; Example .......; Yes
; ===============================================================================================================================
Func _Singleton($sOccurenceName, $iFlag = 0)
    Local $ERROR_ALREADY_EXISTS = 183, $handle, $lastError
    
    $sOccurenceName = StringReplace($sOccurenceName, "\", "") ; to avoid error
    ;$handle = DllCall("kernel32.dll", "int", "CreateSemaphore", "int", 0, "long", 1, "long", 1, "str", $sOccurenceName)
    $handle = DllCall("kernel32.dll", "int", "CreateMutex", "int", 0, "long", 1, "str", $sOccurenceName)
    $lastError = DllCall("kernel32.dll", "int", "GetLastError")
    If $lastError[0] = $ERROR_ALREADY_EXISTS Then
        If $iFlag = 0 Then
            Exit -1
        Else
            Return SetError($lastError[0], $lastError[0], 0)
        EndIf
    EndIf
    Return $handle[0]
EndFunc   ;==>_Singleton

:)


Valuater's AutoIt 1-2-3, Class... Is now in Session!For those who want somebody to write the script for them: RentACoder"Any technology distinguishable from magic is insufficiently advanced." -- Geek's corollary to Clarke's law

Share this post


Link to post
Share on other sites

I just want to note that while I originally wrote Singleton(), the version that appears is nothing like mine anymore. That's rather annoying.

Share this post


Link to post
Share on other sites

You need a copy of PSTools, or SCHEDTASKS.exe, etc... just because only one can get on the Console at time, doesn't mean others can't run remote processes on your workstation. Remove the firewalls and put it on a direct internet connection for a while and you will be AMAZED at the number of remote processes that can be initiated by other users... :)

I know that processes can be run by remote users, I'm only referring to remote users logging in to the machine. Sorry for the confusion.

I shouldn't have gone there anyway. The _Singleton() function doesn't check process names, it creates a named mutex. If the script tries to run again, re-creation of the mutex with the same name fails and triggers the response called for by $iflag. Are you running it with $iflag = 1? If so, then _Singleton only returns 0 and sets @error rather than exiting the scirpt. Using a variable instance name in the call to _Singleton() would also break it because the various names of the mutexes created will not conflict with each other.

Post the line from you script where you call the _Singleton function, and enough code to see how you generate the instance name (if it's not a static string).

Here's the code, I'm not using $iFlag.

_Singleton( "OneAndOnlyOne" )
(it was in the attachement at the begining of the post)

Valik's solution of adding the 'Global\' sounds interesting, I'll give it a try.

Problem with that is the third line of code in the _Singleton() UDF strips the backslash:

And I will take this into consideration.

Maybe _Singleton, by default, should add the 'Global\' to the $sOccurenceName to create a true global mutex.

Share this post


Link to post
Share on other sites

Maybe _Singleton, by default, should add the 'Global\' to the $sOccurenceName to create a true global mutex.

No, I'm not going to modify that. All I'll do is remove the line that strips the slash. If you need a global object, you'll need to specify the name yourself. I don't want Singleton modifying the name of the object at all. So for the time being you should be to use your own custom version of _Singleton() with the StringReplace() stripped out and an object with "Global\" at the beginning of the name.

Share this post


Link to post
Share on other sites

#10 ·  Posted (edited)

Here's the code, I'm not using $iFlag.

_Singleton( "OneAndOnlyOne" )
(it was in the attachement at the begining of the post)

Valik's solution of adding the 'Global\' sounds interesting, I'll give it a try. And I will take this into consideration.

Maybe _Singleton, by default, should add the 'Global\' to the $sOccurenceName to create a true global mutex.

Your code should have worked if the users shared context for the mutex, so they must not. You could just take the UDF code and do your own version of _Singleton() without the stripping of the backslash, or add a global switch. Since whoever modded it to Valik's annoyance didn't bother to put their name on it, we don't get to ask what error they thought it would avoid.

Assuming there must have been some reason for removing the backslashes, but that "Global\" is safe, I modded it this way to add an $iGlobal flag:

; ==========================================================================
; Name...........: _Singleton
; Description ...: Check if no other occurence is running
; Syntax.........: _Singleton($sOccurenceName[, $iFlag = 0[, $iGlobal = 0]])
; Parameters ....: $sOccurenceName - String to identify the occurrence of the script
;                  $iFlag          - Action if @error
;                  |0 - Exit if occurrence already exists
;                  |1 - Return if occurrence already exists
;                   $iGlobal        - Adds global context to the mutex name used
; Return values .: Success      - 1
;                  Failure      - 0
; Author ........: Valik
; Modified.......: By Unknown
; Modified.......: By PsaltyDS to add $iGlobal flag 01/03/08
; Remarks .......: Backslashes are removed from the instance name, but "Global\" will 
;                   be added back if the $iGlobal flag is set
; Related .......:
; Link ..........;
; Example .......; Yes
; ==========================================================================
Func _Singleton($sOccurenceName, $iFlag = 0, $iGlobal = 0)
    Local $ERROR_ALREADY_EXISTS = 183, $handle, $lastError
   
    $sOccurenceName = StringReplace($sOccurenceName, "\", "") ; to avoid error
    If $iGlobal Then $sOccurenceName = "Global\" & $sOccurenceName
    $handle = DllCall("kernel32.dll", "int", "CreateMutex", "int", 0, "long", 1, "str", $sOccurenceName)
    $lastError = DllCall("kernel32.dll", "int", "GetLastError")
    If $lastError[0] = $ERROR_ALREADY_EXISTS Then
        If $iFlag = 0 Then
            Exit -1
        Else
            Return SetError($lastError[0], $lastError[0], 0)
        EndIf
    EndIf
    Return $handle[0]
EndFunc   ;==>_Singleton

Try that.

:)

Edit: Oops, didn't see Valik's last before posting this.

Edited by PsaltyDS

Valuater's AutoIt 1-2-3, Class... Is now in Session!For those who want somebody to write the script for them: RentACoder"Any technology distinguishable from magic is insufficiently advanced." -- Geek's corollary to Clarke's law

Share this post


Link to post
Share on other sites

Most likely the StringReplace() thing is to allow people to use shitty-names like @ScriptFullPath or something. And I'm pretty sure I know who made the change (if I didn't make it myself which is always possible).

But no, Global\ *must* be added. It's not exactly clear, but in a server environment, it's expected that multiple instances of applications will be launched so objects like this *are not* put in the global namespace so that those applications can run concurrently under different users. So in that environment, it's necessary to explicitly request a global object.

Share this post


Link to post
Share on other sites

Oh hell, I was afraid of this. It's no big deal to create something in the "Global\" namespace but it occurred to me and testing has confirmed, the other user isn't going to have access rights to the object. So instead of ERROR_ALREADY_EXISTS you'll get ERROR_ACCESS_DENIED. Now, there's 2 things that can be done. The first is the lazy way out and I really don't recommend it because it could be a false positive. But you could just check for ERROR_ACCESS_DENIED as well as ERROR_ALREADY_EXISTS. But, if the user running your script doesn't have enough access rights to create the object in the first place, you'll also get the error as well.

The correct thing to do isn't really that hard but it's a few more DllCall()'s. The problem is, _Singleton() doesn't specify any security information, so Windows automatically uses the process and thread's access tokens to limit who can access the object. UserB can't access a _Singleton() object created by UserA. It's fairly simple to fix, you'll need to create your own security structure with a NULL DACL. This tells Windows "let everybody have access". Thus, UserA creates it, UserB can use it and so on. You still have to use the "Global\" name, too.

And you're in luck. The reason I say this is easy is because I've spent the last several days reading about this stuff. As a result, I'll write a new function for you, _SingletonEx(). I'll post it later after I write/test it but it will also be in the next version of AutoIt. It'll automatically take care of creating an (in)secure object for you since I don't imagine you care to learn how to do that. You'll need to specify a properly named object (With "Global\" in it) and possibly need to use a flag to turn on the advanced functionality (it'll probably default to working just like _Singleton()).

Stay tuned and I should have a new function for you in a a few hours or a day.

Share this post


Link to post
Share on other sites

Turns out I didn't need a new function. I forgot _Singleton() already had a flag parameter so I just expanded on it. Here's the new version. Tested on Windows XP SP2 with the following scripts:

; SingletonTest.au3 (Needs compiled to C:\SingletonTest.exe)
     _Singleton("Global\TestSingleton", 2)
     MsgBox(4096, "", @UserName)

Local $sTest = "C:\SingletonTest.exe"
     Run($sTest)
     Sleep(1000)
     RunAsSet("user", @ComputerName, "pass")
     Run($sTest)

  ; #FUNCTION# ====================================================================================================

================
  ; Name...........: _Singleton
  ; Description ...: Check if no other occurence is running
  ; Syntax.........: _Singleton($sOccurenceName[, $iFlag = 0])
  ; Parameters ....: $sOccurenceName - String to identify the occurrence of the script.  This string may not contain any \ characters unless you are placing the object in a namespace.
  ;               $iFlag          - Function options.  Bitwise flags, BitOr() them together for the desired effect.
  ;               |0 - Exit if occurrence already exists
  ;               |1 - Return if occurrence already exists
  ;               |2 - Allow the object to be accessed by anybody in the system.  This is useful if specifying a "Global\" object in a multi-user environment.
  ; Return values .: Success      - 1
  ;               Failure     - 0
  ; Author ........: Valik
  ; Modified.......:
  ; Remarks .......: You can place the object in a namespace by prefixing your object name with either "Global\" or "Local\".  "Global\" objects combined with the flag 2 are useful in multi-user environments.
  ; Related .......:
  ; Link ..........;
  ; Example .......; Yes
  ; ====================================================================================================

===========================
   Func _Singleton($sOccurenceName, $iFlag = 0)
       Local Const $ERROR_ALREADY_EXISTS = 183
       Local Const $SECURITY_DESCRIPTOR_REVISION = 1
       Local $handle, $lastError, $pSecurityAttributes = 0
   
       If BitAND($iFlag, 2) Then
          ; The size of SECURITY_DESCRIPTOR is 20 bytes.  We just
          ; need a block of memory the right size, we aren't going to
          ; access any members directly so it's not important what
          ; the members are, just that the total size is correct.
           Local $structSecurityDescriptor = DllStructCreate("dword[5]")
           Local $pSecurityDescriptor = DllStructGetPtr($structSecurityDescriptor)
          ; Initialize the security descriptor.
           Local $aRet = DllCall("advapi32.dll", "int", "InitializeSecurityDescriptor", _
               "ptr", $pSecurityDescriptor, "dword", $SECURITY_DESCRIPTOR_REVISION)
           If Not @error And $aRet[0] Then
              ; Add the NULL DACL specifying access to everybody.
               $aRet = DllCall("advapi32.dll", "int", "SetSecurityDescriptorDacl", _
                   "ptr", $pSecurityDescriptor, "int", 1, "ptr", 0, "int", 0)
               If Not @error And $aRet[0] Then
                  ; Create a SECURITY_ATTRIBUTES structure.
                   Local $structSecurityAttributes = DllStructCreate("dword;ptr;int")
                  ; Assign the members.
                   DllStructSetData($structSecurityAttributes, 1, DllStructGetSize($structSecurityAttributes))
                   DllStructSetData($structSecurityAttributes, 2, $pSecurityDescriptor)
                   DllStructSetData($structSecurityAttributes, 3, 0)
                  ; Everything went okay so update our pointer to point to our structure.
                   $pSecurityAttributes = DllStructGetPtr($structSecurityAttributes)
               EndIf
           EndIf
       EndIf
   
       $handle = DllCall("kernel32.dll", "int", "CreateMutex", "ptr", $pSecurityAttributes, "long", 1, "str", $sOccurenceName)
       $lastError = DllCall("kernel32.dll", "int", "GetLastError")
       If $lastError[0] = $ERROR_ALREADY_EXISTS Then
           If BitAND($iFlag, 1) Then
               Return SetError($lastError[0], $lastError[0], 0)
           Else
               Exit -1
           EndIf
       EndIf
       Return $handle[0]
   EndFunc  ;==>_Singleton

Share this post


Link to post
Share on other sites

Cool! :)

I'm not the one who needed this, but it's been very educational, as usual.

Thanks again, Valik.

:P


Valuater's AutoIt 1-2-3, Class... Is now in Session!For those who want somebody to write the script for them: RentACoder"Any technology distinguishable from magic is insufficiently advanced." -- Geek's corollary to Clarke's law

Share this post


Link to post
Share on other sites

The new code works like a charm.

Thanks Valik and PsaltyDS for your help.

:)

Share this post


Link to post
Share on other sites

#16 ·  Posted (edited)

Valik,

Thanks for all the great work you've done in the libraries. I especially found the _Singleton() function extremely useful this weekend...after a couple of tweaks.

Now, hear me out before the flaying of my flesh commences. Perhaps I will deserve it.

I know that the original purpose (your purpose) for the _Singleton() function was simply to ensure that a single copy of the script would run.

I made several *minor* modifications to the _Singleton() function and came up with a way to add two features:

1. The ability to, in a loop, use _Singleton() to wait for another process to exit. This required fixing a bug in the original, which was the creation of duplicate handles to the same mutex if the _Singleton() function is called more than once from inside a process. Sounds crazy, I know...but when you call CreateMutex with a name that matches an existing mutex...it actually does create a different handle to the mutex, while also returning the $ERROR_ALREADY_EXISTS error. So _Singleton() now discards that duplicate handle before exiting when called with the "1" flag.

2. The ability to use _Singleton() to protect critical regions by allowing the process to discard the mutex handle it created.

It's not quite full on mutex support, but it is quite a bit more useful to me.

To prevent confusion, my functions are currently named _SingletonXX() and _SingletonReleaseXX(). I have included them below.

Let the flaying of my flesh begin.

-brendan

CODE
; #FUNCTION# ====================================================================================================

================

; Name...........: _SingletonXX

; Description ...: Check if no other occurence is running. 20081221 - if one is, you can now continue checking for its exit in a wait loop, or even pair the function with _SingletonReleaseXX() to protect critical regions of code.

; Syntax.........: _SingletonXX($sOccurenceName[, $iFlag = 0])

; Parameters ....: $sOccurenceName - String to identify the occurrence of the script. This string may not contain any \ characters unless you are placing the object in a namespace.

; $iFlag - Action if @error

; |0 - Exit if occurrence already exists

; |1 - Return if occurrence already exists

; |2 - Allow the object to be accessed by anybody in the system. This is useful if specifying a "Global\" object in a multi-user environment.

; Return values .: Success - handle to mutex (new 20081221 by bhoar)

; Failure - 0

; Author ........: Valik

; Modified.......: 20081221 by bhoar - incorporates original function but also allows for waiting for other script to exit before continuing or even protecting critical regions of code.

; Remarks .......: You can place the object in a namespace by prefixing your object name with either "Global\" or "Local\". "Global\" objects combined with the flag 2 are useful in multi-user environments.

; Related .......:

; Link ..........;

; Example .......; Yes

; ====================================================================================================

===========================

Func _SingletonXX($sOccurenceName, $iFlag = 0)

Local Const $ERROR_ALREADY_EXISTS = 183

Local Const $SECURITY_DESCRIPTOR_REVISION = 1

Local $handle = 0, $lastError, $pSecurityAttributes = 0

Local $hMutex = 0

If BitAND($iFlag, 2) Then

; The size of SECURITY_DESCRIPTOR is 20 bytes. We just

; need a block of memory the right size, we aren't going to

; access any members directly so it's not important what

; the members are, just that the total size is correct.

Local $structSecurityDescriptor = DllStructCreate("dword[5]")

Local $pSecurityDescriptor = DllStructGetPtr($structSecurityDescriptor)

; Initialize the security descriptor.

Local $aRet = DllCall("advapi32.dll", "int", "InitializeSecurityDescriptor", _

"ptr", $pSecurityDescriptor, "dword", $SECURITY_DESCRIPTOR_REVISION)

If Not @error And $aRet[0] Then

; Add the NULL DACL specifying access to everybody.

$aRet = DllCall("advapi32.dll", "int", "SetSecurityDescriptorDacl", _

"ptr", $pSecurityDescriptor, "int", 1, "ptr", 0, "int", 0)

If Not @error And $aRet[0] Then

; Create a SECURITY_ATTRIBUTES structure.

Local $structSecurityAttributes = DllStructCreate("dword;ptr;int")

; Assign the members.

DllStructSetData($structSecurityAttributes, 1, DllStructGetSize($structSecurityAttributes))

DllStructSetData($structSecurityAttributes, 2, $pSecurityDescriptor)

DllStructSetData($structSecurityAttributes, 3, 0)

; Everything went okay so update our pointer to point to our structure.

$pSecurityAttributes = DllStructGetPtr($structSecurityAttributes)

EndIf

EndIf

EndIf

$handle = DllCall("kernel32.dll", "int", "CreateMutex", "ptr", $pSecurityAttributes, "long", 1, "str", $sOccurenceName)

$hMutex = $handle[0]

$lastError = DllCall("kernel32.dll", "int", "GetLastError")

If $lastError[0] = $ERROR_ALREADY_EXISTS Then

If BitAND($iFlag, 1) Then

;20081221 - First release the mutex! The original Singleton() code created a new, duplicate handle *every time* you looked for a mutex.

; Granted, that worked ok for the original purpose where you'd inevitably exit immediately if another script was found running, which destroyed the mutex...

; But if you wanted to wait on another process or even use the mutex to protect critical regions, you definitely don't want multiple handles to the same mutex

; name lying around...

_SingletonReleaseXX($hMutex)

Return SetError($lastError[0], $lastError[0], 0)

Else

Exit -1

EndIf

EndIf

; 20081221 - Remember to keep track of the return value, it's the handle to the mutex you now own...

Return $hMutex

EndFunc ;==>_SingletonXX

; #FUNCTION# ====================================================================================================

================

; Name...........: _SingletonReleaseXX

; Description ...: Accessory function to _SingletonXX() and can be paired with _SingletonXX() to protect critical regions of code.

; Syntax.........: _SingletonReleaseXX($hMutex)

; Parameters ....: $hMutex - handle to mutex originally generated by _SingletonXX()

; Return values .: None

; Author ........: bhoar

; Modified.......:

; Remarks .......: This is a bridge approach between the original untradeable lock of _Singleton() and real mutex requesting and trade between processes, but without diverging too far from the current _Singleton() function

; Remarks .......: Technically this is actually destorying the mutex, not just releasing it.

; Related .......:

; Link ..........:

; Example .......:

; ====================================================================================================

===========================

Func _SingletonReleaseXX($hMutex)

Local $handle

$handle = DllCall("kernel32.dll", "int", "ReleaseMutex", "int", $hMutex)

$handle = DllCall("kernel32.dll", "int", "CloseHandle", "int", $hMutex)

EndFunc ;==>_SingletonReleaseXX

And an example of how I use it here:

$hMutex = _SingletonXX("AnyDVDAutomation", 1)
If $hMutex = 0 Then
    ConsoleWrite("Waiting up to 20s to get mutex for AnyDVD window control." & @LF & @LF)
    $mutexTimer = TimerInit()
    While $hMutex = 0 And TimerDiff($mutexTimer) < 20000
        Sleep(1000)
        $hMutex = _SingletonXX("AnyDVDAutomation", 1)
    WEnd
EndIf

If $hMutex = 0 Then
    ConsoleWrite("Unable to get mutex for AnyDVD control...exiting." & @LF)
    Exit ($rcReject)
EndIf

;;;;
;;;;  CRITICAL REGION OF CODE GOES HERE
;;;;

$rc=_SingletonReleaseXX($hMutex)
Edited by bhoar

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!


Register a new account

Sign in

Already have an account? Sign in here.


Sign In Now
Sign in to follow this  
Followers 0