A Smalltalk image is composed largely of objects which reference other objects. When an object is no longer referenced from a root object such as Smalltalk, such that it cannot be reached, then it becomes eligible for garbage collection. The presence of even a single reference from another "live" object will prevent an object from being garbage collected. On occasion we need to reference an object, perhaps to maintain a registry of all existing instances in a collection, but do not want to prevent it from being garbage collected. Such data structures need to maintain their invariants when elements are garbage collected.
Mark specific objects as containing weak references to other objects. Weak references are ignored by the garbage collector when it is establishing which objects are reachable, and therefore do not prevent objects from being collected. Weaklings can receive a bereavement notification informing them of losses, enabling them to undertake the necessary repairs.
WeakSet illustrates all the points above.
Weak sets need to receiver bereavement notifications, so the class is marked as having mourning instances in the class initialize method:
initialize "Initialize the receiver. Bereavement notifications are required by the receiver's instances to effect repairs." self makeMourner
WeakSet instances are initialized to be weak as follows:
initialize "Private - Instance variable initialization. Ensure the receiver is weak." super initialize. self beWeak
Weak sets are hashed collections with internal overflow, so in order to maintain the set invariants, we must partially or wholly rehash the collection. Hence the complexity of this method:
elementsExpired: anInteger "Private - Handle the bereavement(s) that the receiver suffered by maintaing the Set invariants (mainly that collision chains are terminated by nils)." "Private - Handle the bereavement(s) that the receiver suffered by maintaing the Set invariants (mainly that collision chains are terminated by nils)." | deathAt capacity corpse | corpse := DeadObject current. mutex critical: [ capacity := self capacity. tally := tally - anInteger. deathAt := self basicIdentityIndexOf: corpse from: 1 to: capacity. (anInteger > (self size // 4)) ifTrue: [ "Rehash the collection if a large number of losses..." [deathAt > 0] whileTrue: [ self basicAt: deathAt put: nil. deathAt := self basicIdentityIndexOf: corpse from: deathAt+1 to: capacity]. self rehash] ifFalse: [ "... otherwise remove the losses individually" [deathAt > 0] whileTrue: [ self basicAt: deathAt put: nil. self fixCollisionsFrom: deathAt. "We must start searching from the same slot in case we've moved a corpse there" deathAt := self basicIdentityIndexOf: corpse from: deathAt to: capacity]]]. "Inform any dependents" self trigger: #elementsExpired: with: anInteger
Most of the enumeration behaviour of collections is provided by the #do: method, so we override this to remove the weak behaviour flag for the duration of the enumeration:
do: operation "Evaluate monadic value argument, operation, for each of the element of the receiver. Answers the receiver. Implementation Note: We override this message because if the user supplied operation puts the active process to sleep (i.e. wait on some Semaphore) a GC may occur, losses may be suffered, and because the mutex prevents the undertaker from entering the critical section in #elementsExpired: we may subsequently enumerate over Corpses and treat them as elements. In order to guard against this we temporarily revoke the receiver's status as a weak object. Note that GC's which nil weak references can only be initiated from idle time, so there is no danger of corpses appearing in the receiver if operation does not put the active process to sleep." | mask | mutex critical: [ mask := self beStrong. [super do: operation] ensure: [self setSpecialBehavior: mask]]