Port locking

Topics: Developer Forum
Jan 24, 2007 at 11:44 AM
I noticed that port instances share a single static lock. I guess that's an optimisation. A couple of comments:

1. In the Port.Push method, the lock is taken and the selectors invoked. As well as locking all ports in the domain, you're relying on all code downstream of Selector.OnSchedule to at least behave and preferably have no inter-thread dependencies. You can nearly do this, except for predicates which are invoked in some selectors and will currently run within this app-domain wide lock. A rogue predicate can lock the entire PCR.

It might be better to take a copy of the selectors, release the lock then iterate over the copy. Looks like this would have a big impact on the downstream code of OnSchedule etc though.

2. This is more of a general observation. When I've used the CCR, a lot of my ports/portsets are relatively shortlived. In principle this should mean that actual number of syncblocks required to provide a lock per-port-instance should be quite low. In fact, for some shortlived portsets e.g. portsets servicing a non-persistent choice, you don't need to lock at all.
Coordinator
Jan 24, 2007 at 8:17 PM
Comments inline.

"I noticed that port instances share a single static lock. I guess that's an optimisation."

Yes. I ~attempted to explain my current thinking in one the FAQ posts.

"1. In the Port.Push method, the lock is taken and the selectors invoked. As well as locking all ports in the domain, you're relying on all code downstream of Selector.OnSchedule to at least behave and preferably have no inter-thread dependencies. You can nearly do this, except for predicates which are invoked in some selectors and will currently run within this app-domain wide lock. A rogue predicate can lock the entire PCR."

Yes. Good observation. At 20,000ft, I gain the lock in Push and hold it until we schedule something and release it whether we schedule something or not. The handler then runs without any lock. If you need some shared sync, you need to do those locks yourself. An interleave is the exception as it gives you reader/writer lock symantics (at least that is the intent). The potential for a block in the predicate is real. I wear two hats on this kind of issue. As a library developer, this makes you very nervous. As a user of the library, I understand I share responsability to make my program behave correctly. We can find spots in the BCL (I think) where we can shoot ourselfs and ultimately need to do the right thing and be aware of the issue. Probably need a warning in the docs. Not sure I see a better alternative here, but would be interested in any ideas.

"It might be better to take a copy of the selectors, release the lock then iterate over the copy. Looks like this would have a big impact on the downstream code of OnSchedule etc though."

Maybe. However I think this could kill perf. You would end up releasing the lock and taking it again for each Push. We are in and out of the lock so fast, contention should not be an issue and I would think you would ultimately increase odds of contention instead of the other way round. Have an open mind however. My current goal is simplicity and least number of locks taken as possible.

"2. This is more of a general observation. When I've used the CCR, a lot of my ports/portsets are relatively shortlived. In principle this should mean that actual number of syncblocks required to provide a lock per-port-instance should be quite low. In fact, for some shortlived portsets e.g. portsets servicing a non-persistent choice, you don't need to lock at all."

Maybe and I thought about that. Few issues that could make this hard. 1) You port needs a lock to push and pop items as you can't know before hand what or how many arbiters will be attached to a port. For that matter, you may never use any Selectors, but just Push and Pop on different threads and use the Port as a thread safe queue. You could derive another Port type, but not sure the gains on that. So if we need the lock anyway for Port, we may as well use that same lock for DQ work items. Otherwise we would need to take two locks for every push (1 for Port and 1 for DQ). This may have some gains in a few cases, but I think would be slower in the common usage cases and more complex to reason about for correctness. It is something I will think about more.

Great points. Thank you for your interest.
--
William