NI TestStand

cancel
Showing results for 
Search instead for 
Did you mean: 

Issue with TestStand Semaphore

Solved!
Go to solution

Hello,

 

I'm working in a project which uses TestStand 2014 32bits and we use semaphores in order to control some equipments.

 

So, when we create a semaphore with maximum threads as 1, we acquire it, the count goes to 0, and no one else can acquire that semaphore. The issue is that if we release the semaphore more then once, the count goes to 2 instead of staying at 1, which will then make the semaphore able to be acquired twice (breaking everything else).

 

We do 2 releases (sometimes) because there are some steps inside the sequence that may break and skip to the Cleanup of the sequence.

 

Is this the designed semaphore? Shouldn't it have a maxium count of 1 always (even with more then one release)?

 

Thanks!

 

EDIT: Attached example sequence file.

0 Kudos
Message 1 of 11
(4,220 Views)

Counts increasing/decreasing behaviour seems to be correct - just double check help topic.

But for equipment lock between threads, use Lock step type - it will do exactly what is needed. While one thread will use it, the second one will wait for it. If you need to share hardware between parallel threads/ test sockets, this is the right choice.

 

Hopefully, it'll help.

 

Sincerely, kosist90

 

logos_middle.jpg

0 Kudos
Message 2 of 11
(4,196 Views)

Hi Kosist90,

Thank you for your input.

 

According to the Help Topic, in the second paragraph, I read:

 A semaphore with an initial count of one behaves like a lock because a one-count semaphore restricts access to a single thread at a time. Unlike a lock, however, a thread cannot acquire a one-count semaphore multiple times without first releasing the semaphore after each acquire. When a thread attempts to acquire the semaphore a second time without releasing it, the count is zero, and the thread blocks.

 

This is what we were trying to do... I believe that once we set the Initial Count to 1, it should not ever increase past 1.

 

Anyway, we tried using locks, and it seems that if we acquire it once, it will work normally.

However, we noticed an odd behavior in this situation:

  1. Lock acquired in Thread1
  2. Lock acquire request in Thread2 (Thread2 will hold)
  3. Lock acquired AGAIN in Thread1 (imagine a loop)
  4. Lock released in Thread1
  5. Thread2 will stay stopped and will not proceed...

Seems like Thread2 would need Thread1 to release the lock twice.

That also seems not to be correct, but maybe it is the design.

 

Any other ideas or suggestions about how to use it?

Our automation is quite big, so it is hard to keep in mind all Acquire and Release requests and to avoid a double acquire/release.

 

TLDR: We would like to use semaphore as described in the second paragraph of the Help Topic.

0 Kudos
Message 3 of 11
(4,164 Views)
Solution
Accepted by topic author GuiDeo

The way semaphores work in TestStand is the standard behavior for semaphores.

 

I think the best solution to the problem you are having is to ensure your sequences never incorrectly try to release the semaphore or lock when they don't own it. Luckily there is a feature of TestStand designed to make this easier to accomplish.

 

I recommend you do the following:

1) Switch to using Lock instead of Semaphore. It's a much better fit for your use case. Unlike Semaphore though, it does support nesting so you should consider if that is ok for your use case.

2) Rather than try to add an unlock to your cleanup, use the "Lock Operation Lifetime: Same as Sequence" setting, which is the default, for the lock operation on the lock. What that does is automatically unlock the lock at the end of the sequence if you haven't already done so.

3) If you need to unlock before the end of the sequence use the Lock's "Early Unlock" operation, just once in your sequence per Lock Operation.

 

If you really want to keep using semaphore:

1) Use the Auto Release option of the Acquire() instead of explicitly calling release. That will ensure that one and only one release is done per-acquire.

2) semaphore doesn't have anything equivalent to Early Unlock so you can't do both manual releases and auto-releases. If you need that, I recommend using Locks instead.

 

Hope this helps,

-Doug

0 Kudos
Message 4 of 11
(4,142 Views)


Anyway, we tried using locks, and it seems that if we acquire it once, it will work normally.

However, we noticed an odd behavior in this situation:

  1. Lock acquired in Thread1
  2. Lock acquire request in Thread2 (Thread2 will hold)
  3. Lock acquired AGAIN in Thread1 (imagine a loop)
  4. Lock released in Thread1
  5. Thread2 will stay stopped and will not proceed...

Seems like Thread2 would need Thread1 to release the lock twice.

That also seems not to be correct, but maybe it is the design.

 



This is because locks support nesting (i.e. being acquired multiple times from the same thread). For every lock operation there needs to be an unlock. That is how critical sections and mutexes generally work. To manage things more automatically and thus reduce bugs with the use of these primitives, I recommend using the automatic unlock behaviors of the TestStand synchronization primitives as I described in my previous post.

 

Hope this helps,

-Doug

0 Kudos
Message 5 of 11
(4,141 Views)

To more clearly explain how you can correctly handle a loop case with locks. See the following order of steps:

 

Step 1) Loop Start

Step 2) Lock the Lock with "Same as Sequence" Lock operation lifetime so that it will be automatically unlocked if sequence terminates before getting to the unlock call.

Step 3-n) Do stuff that requires the lock

Step n + 1) Do Early Unlock on the lock when you are done with it.

Step n + m) Do stuff in the loop that doesn't require the lock

Step n + m + 1) Loop End

0 Kudos
Message 6 of 11
(4,138 Views)

Hey dug9000, thanks for the input.

I believe that the Lock explanation that you gave seems to be the best fit for our case. I will try to use that and see how it goes. Thanks!


Just one final question about locks, would you be able to tell me what happens if we acquire the Lock multiple times (by mistake, for example) and then use auto-release? Would all the "acquires" be released upon the end of the sequence?

 

Thanks again!

 

EDIT: I forgot, we could'nt use the "Same as Sequence" for the semaphore at least, because our automation has kind of a "pool of semaphores" where it would iterate and then acquire one given available semaphore. This is handled by a particular sequence which would make the semaphore available before it is actually used (because the sequence that performed the acquire ended).

We would need to see how that "pool" goes with Locks.

 

I would still like to know why can a Semaphore Count be increased past the set "Initial Semaphore Count (Maximum Simultaneous Non-Blocking Acquires)". It still doesn't make sense to me.

0 Kudos
Message 7 of 11
(4,124 Views)

1) Yes, the auto-releasing will unlock the lock for each lock operation that was done, so, for example, if you lock the lock 5 times within a sequence with the "Same as Sequence" lock operation lifetime. TestStand will unlock the lock 5 times when the sequence completes. It also takes into account whether or not an "Early Unlock" operation was done. If an Early Unlock operation is done, then it does not unlock it again when the sequence completes. TestStand keeps track of the fact it was already unlocked by the Early Unlock operation.

 

2) I don't understand the purpose of your semaphore pool. You might want to reconsider whether or not it is really necessary or is perhaps causing bugs. TestStand can already dynamically allocate synchronization primitives with a lifetime you specify.

 

3) Semaphores are often used in situations where there are 'n' of some resource or you want up to 'n' threads working in parallel. In those situations you want to ensure that only 'n' threads can actively use that resource at a time and when the 'n + 1'th thread and higher try to acquire such a semaphore they should block until one of the existing users of the resource releases the semaphore to indicate they are done with it. It is not necessarily intended to take the place of a lock. That is what the Lock primitive is for. That is also why the Semaphore step type is under an "Advanced" folder.

 

4) Really it's a programming mistake to release or unlock a synchronization primitive too many times. It's best to ensure it cannot happen in any code paths in your program or sequence. You might get lucky with a synchronization primitive where it ends up being harmless, but that is not usually the case.

 

Hope this helps,

-Doug

0 Kudos
Message 8 of 11
(4,107 Views)

Just want to add, if "same as sequence" doesn't work for your use case, you can use one of the other options, the most flexible of which lets you specify a TestStand Object Reference variable. When that variable is cleared (by setting to null or another value), or goes out of scope, then the lock will get released.

 

That said, I highly recommend coming up with an architecture where one of the automatic unlocking lifetimes like "same as sequence" works well for you since it will help reduce the likelihood of bugs.

 

-Doug

0 Kudos
Message 9 of 11
(4,105 Views)

Also, I would like to point out that in other frameworks, where a maximum count can be specified for a semaphore, it's usually an exception or error to increment the count past the maximum. It's not harmless or ignored.

 

For example, with the .NET API:

https://msdn.microsoft.com/en-us/library/cckchaw6(v=vs.110).aspx

 

A SemaphoreFullException occurs if you try to increment the semaphore past its maximum count.

 

Also the documentation on that page states, "If a SemaphoreFullException is thrown by the Release method, it does not necessarily indicate a problem with the calling thread. A programming error in another thread might have caused that thread to exit the semaphore more times than it entered."

 

So in general, mismatching acquires and releases is considered a bug, not something a synchronization primitive is meant to compensate for. I suspect the specifying of a maximum count for the .NET primitive is mostly to help catch such bugs more easily, since they will be more likely to cause exceptions, not to mitigate them.

 

Hope this helps,

-Doug

0 Kudos
Message 10 of 11
(4,102 Views)