Some objects need to be shared between multiple independent processes, and protected so that attempts to mutate the object from one process do not impinge on the attempts to access or mutate the object from other processes. Access to the state of the object must be synchronised so that corruption and dirty reads do not occur.
A relatively simple example in the base system is SharedSet (a process safe subclass of Set). New SharedSets are initialized as follows:
initialize "Instance variable initialization. The mutex protects against concurrent access from multiple processes, but permits the same process to make multiple entries." super initialize. mutex := Mutex new
The Set>>add: method is overridden as follows:
add: newObject "Include newObject as one of the elements of the receiver. Answer newObject." ^mutex critical: [super add: newObject]
Note that the value of the critical section is the value of the expression inside the block, so there is no need to perform a ^-return inside the critical section and cause an unwind to be set in motion.
#do: is overridden in a similar manner:
do: operation "Evaluate monadic value argument, operation, for each of the elements (non-nil members) of the receiver. Answers the receiver. N.B. It is important that operation does not put the active process to sleep (i.e. wait on some Semaphore) as this will prevent other Smalltalk processes from accessing the receiver for a potentially long time, and if a weak subclass, may prevent the removal of Corpses from taking place. In the case of weak subclasses, if the putting the active process to sleep is unavoidable, then the weak status should be removed until the end of the critical block (e.g. send #beStrong at the start of the block, and #beWeakWithNotify at the end of the block)." mutex critical: [super do: operation]
SharedSet conservatively overrides #asArray as follows:
asArray "Answer an Array whose elements are those of the receiver (ordering is possibly arbitrary). Must implement as critical section as otherwise Array size might be wrong." ^mutex critical: [super asArray]
This is apparently protected by the overridden #do: method, but the size of the Array could turn out to be wrong if resized by another process during the execution of Collection>>asArray (below) between the point where the size of the SharedSet is taken, and the #do: message being sent.
asArray "Answer an Array whose elements are those of the receiver. (ordering is that of the #do: operation as implemented by the receiver)." | anArray i | anArray := Array new: self size. i := 1. self do: [:e | anArray at: i put: e. i := i + 1]. ^anArray
To avoid the possibility of a client supplied "if absent" block raising an exception inside a critical section, SharedSet>>remove:ifAbsent: makes use of a unique object (or cookie) to identify the "not found" case, and then evaluates the client supplied exception handler when the cookie is detected, as follows:
remove: oldElement ifAbsent: exceptionHandler "If oldElement is one of the receiver's elements, then remove it from the receiver and answer it (as Sets cannot contain duplicates, only one element is ever removed). If oldElement is not an element of the receiver (i.e. no element of the receiver is #= to oldObject) then answer the result of evaluating the niladic valuable, exceptionHandler." | answer | answer := mutex critical: [super remove: oldElement ifAbsent: [AbsentCookie]]. ^answer == AbsentCookie ifTrue: [exceptionHandler value] ifFalse: [answer]
SharedSet overrides other methods as necessary - see the documentation method #overrideStrategy for explanation of why certain methods are overridden, and others not.