Hello,
I have a problem that has been plaguing me for awhile now, I have come up with a solution which I will detail below, and although it seems to be working well I'm not super enthusiastic about it from a design point of view and I'm curious if anyone would have any recommendations about a better way to do this.
Basically, I have a shared resource, lets just say it's a directory of files. I have a single object that manages this resource (we'll call it an instance of class BossOfEverthing). BossOfEverthing handles adding, deleting, modifying and retrieving data from the files within that directory. When another object wants to access the shared resource it does so through the BossOfEverthing instance. BossOfEverthing uses locks internally since its client objects can and do exist on separate threads. BossOfEverthing also maintains an array of references to client objects that observe the BossOfEverthingClient protocol. When BossOfEverthing is about to change anything about the shared resource (perhaps due to a request from one of its clients) it notifies all of the clients ahead of time by calling an appropriate selector for each client so that they can all have a chance to respond first. BossOfEverthing is in fact the Boss, ie. the clients have no say so as to whether they approve of the shared resource being changed, but they are given the chance to perform any requisite cleanup activities first. The way I look at it, it's as if BossOfEverthing has many 'delegates'. The difference between what I need to do and the normal delegation pattern is that:
- There are many delegates/clients
- Delegates/clients are created and destroyed many times throughout the lifetime of the BossOfEverthing instance (which in fact exists throughout the lifetime of the entire application).
When an object wants to be a client of the BossOfEverthing instance it calls [BossOfEverthing addMeToYourClientsList:] (usually from its init method) and when an object wants to stop being a client of BossOfEverthing it calls [BossOfEverthing removeMeFromYourClientList:] (from its dealloc method). This way BossOfEverthing knows who to notify (and on what thread) when the shared resource changes.
Normally I would have used notifications or KVO to message the clients, but the hitch is that all of the clients need to have a chance to respond appropriately BEFORE the resource actually changes (like in a normal delegation pattern). Neither notifications or KVO block while the receivers are responding.
OK, that all seems great, but take this scenario:
- BossOfEverthing is about to change the shared resource, eg. [BossOfEverthing changeSomething] is called by some object on thread 1
- [BossOfEverthing changeSomething] acquires the lock associated with the client array
- [BossOfEverthing changeSomething] begins iterating through the client array and calling each client's somethingIsAboutToBeChanged method on the appropriate thread for each client
- In the meantime one of the clients (clientX) is about to go out of existence, so it calls [BossOfEverthing removeMeFromYourClientList:] on thread 2 from within its dealloc method
- [BossOfEverthing removeMeFromYourClientList:] attempts to acquire the lock associated with the client array on thread 2 so that it can remove clientX
What happens here is that I end up in a deadlock because:
- [BossOfEverthing changeSomething] (who has acquired the lock on thread 1) is waiting for [clientX somethingIsAboutToBeChanged] to return from thread2
- Thread 2 is stuck waiting to acquire the lock which is currently owned by [BossOfEverthing changeSomething] on thread 1 and is unable to respond to its somethingIsAboutToBeChanged method
Here's what I have done to remedy this:
- Rather than clientX calling [BossOfEverthing removeMeFromYourClientList:] from its dealloc method, it calls it from its release method (only when retainCount==1)
- [BossOfEverthing removeMeFromYourClientList:] rather than waiting on the lock just ATTEMPTS to acquire it and if it can't it returns NO.
- If [BossOfEverthing removeMeFromYourClientList:] returns NO to [clientX release] then [clientX release] calls [self performSelector:@selector(release) withObject:nil afterDelay:0.1], otherwise it just calls [super release]
This way clientX has a chance to respond to any messages that [BossOfEverthing changeSomething] might be sending it and allow [BossOfEverthing changeSomething] to finish up it's business and release the lock, while another call to [clientX release] is queued up in the delay.
The problem that I have with this is that:
- Client objects are of a variety of classes and so I have to copy paste my overriden release method to each of them. There's something that irks me about copying and pasting the same code to multiple classes. If objective-c allowed multiple inheritance then I could perhaps create another class 'BossOfEverthingClient' whose only defined method would be an overriden release.
- This whole procedure seems a bit convoluted.
Anyway, Thanks so much for reading my long winded post and I'll look forward to any input that anyone has. Thanks again!