Pattern: Weakling


Context

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.

Solution

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.

  1. Create a suitable Process Safe Class as the superclass for the weak class. If you are creating a weak version of an existing class, then the process safe class will probably be a subclass of the existing class.
  2. Create the New Class as a subclass of the process safe class. The class must be an indexable pointer (not byte) class, as only indexable pointers can be weak - the named instance variables always hold strong references (Weak References and Finalization).
  3. Mark the new weak class as having instances which require an #elementsExpired: notification by sending the class the #makeMourner message in the class' #initialize method.
  4. Mark the class' new instances as being weak by sending them the #beWeak message. This is usually done by overriding the class #new: method, or the instance #initialize method (see example).
  5. Implement an #elementsExpired: method to handle the bereavements the weakling may suffer (the argument is an integer count of the number of losses the receiver suffered). The job of this method is to repair the damage the caused by the garbage collector converting expired weak references to the corpse object (we call this mourning). Often this may simply involve converting corpses to nils, but it may also (for example in hashed collections) require more extensive repairs. The repairs must be carried out inside a critical section against the Mutex inherited from the process safe superclass.
  6. Override certain superclass operations such as enumerations, and other operations involving the application of user supplied blocks to the receiver. This is to defend the weakling from the appearance of corpses while it is inside an operation. Corpses could appear in the middle of an operation if a user supplied block (perhaps inadvertantly) puts the active process to sleep. The VM's garbage collector does not respect Smalltalk critical sections, so, even when protected by a mutex, if a process allows the system to go idle, a window of opportunity arises for corpses to supplant expired weak references. To prevent this from occurring, one can temporarily remove the weak attribute of the weakling - the garbage collector will then treat all its references as normal references. The weak state of an object is revoked by sending it the #beStrong message, which answers the objects current special behaviour flags. The special behaviour can be restored by sending the object #setSpecialBehavior: with those flags as the argument.

Example

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]]

Consequences

Known Uses

Related Patterns