EphemeronDictionary, evaluateWithFullProtection: vs. accessLock critical:

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
2 messages Options
Reply | Threaded
Open this post in threaded view
|

EphemeronDictionary, evaluateWithFullProtection: vs. accessLock critical:

Joachim Geidel
Hello everyone,

in EphemeronDictionary (and HandleRegistry), some methods are protected
by #evaluateWithFullProtection:, while other methods directly use
"accessLock critical: [...]". I would like to implement a Weakling-based
WeakSet (final destination Public Repository, of course ;-) ), where I
need to use the same technique.

When an exception is raised in a block protected by the accessLock, and
the exception is not handled, all other processes accessing the
EphemeronDictionary are blocked until the situation is resolved. If the
block is protected by #evaluateWithFullProtection:, the RecursionLock
would be available immediately, letting other Processes access the
EphemeronDictionary again. (Please correct me if I'm wrong.)

#at:ifAbsent:, #associationAt:ifAbsent: and #removeKey:ifAbsent: use the
accessLock directly. That makes sense, because an Exception in the
ifAbsent-block could be handled elsewhere and resume or restart the
execution of the block, which should still occur in the protected
section (the exception handler and/or the ifAbsent-block might want to
change the EphemeronDictionary). But shouldn't the methods
#associations, #includesKey: and #localBindings use
#evaluateWithFullProtection:? And why does
WeakDictionary>>associationsDo: use "accessLock critical:" while
EphemeronDictionary>>associationsDo: uses #evaluateWithFullProtection:?

I would expect that all the query methods use
#evaluateWithFullProtection: while all methods which might change the
dictionary (including all methods which have a block as an argument)
should use "accessLock critical:". Is that reasonable?

I'm also wondering about WeakDictionary>>setAccessLock:. When a
WeakDictionary is growing, it makes sure that it uses the same
accessLock afterwards. There is no corresponding code in
EphemeronDictionary. When it grows, it will use a new RecursionLock
afterwards. Could this cause problems during the transition from the old
to the new dictionary?

It seems that there is a bug in EphemeronDictionary>>changeCapacityTo:
In this method, #associationsDo: is protecting the original self, but
the protection does not extend to the final #become:. If finalization
happens to mourn an Ephemeron key just between copying the associations
 and the become:, then the new copy will still contain the now obsolete
Ephemeron. If the VM does not detect that it is still there (after all,
it believes it has already dealt with it), then the Ephemeron will hang
around forever. I don't have the VM code to check this. The problem, if
it actually exists, might cause memory leaks in DependentsFields,
EventHandlers and the SubscriptionRegistry of the Announcement framework.

The following code illustrates the problem:
| dict newDict key ephemeron |
dict := EphemeronDictionary new.
key := String new.
dict at: key put: 1.
ephemeron := dict associationAt: key.
key := nil. "dereference it, probably not necessary here"
"simulate #changeCapacityTo:"
newDict := dict copyEmpty: 10.
dict associationsDo: [:each | newDict noCheckAdd: each].
"simulate finalization during #changeCapacityTo:"
ephemeron mourn.
dict become: newDict.
"see what's left"
dict associations    "OrderedCollection (''->1) - still there"
newDict associations "OrderedCollection () - this is the old self"

Best regards,
Joachim Geidel

Reply | Threaded
Open this post in threaded view
|

Re: EphemeronDictionary, evaluateWithFullProtection: vs. accessLock critical:

Alan Knight-2
Good questions, to which I do not know the answers. However, I've created AR 50998 for the two issues you raise at the end of the message.

At 08:29 AM 7/15/2006, Joachim Geidel wrote:

>Hello everyone,
>
>in EphemeronDictionary (and HandleRegistry), some methods are protected
>by #evaluateWithFullProtection:, while other methods directly use
>"accessLock critical: [...]". I would like to implement a Weakling-based
>WeakSet (final destination Public Repository, of course ;-) ), where I
>need to use the same technique.
>
>When an exception is raised in a block protected by the accessLock, and
>the exception is not handled, all other processes accessing the
>EphemeronDictionary are blocked until the situation is resolved. If the
>block is protected by #evaluateWithFullProtection:, the RecursionLock
>would be available immediately, letting other Processes access the
>EphemeronDictionary again. (Please correct me if I'm wrong.)
>
>#at:ifAbsent:, #associationAt:ifAbsent: and #removeKey:ifAbsent: use the
>accessLock directly. That makes sense, because an Exception in the
>ifAbsent-block could be handled elsewhere and resume or restart the
>execution of the block, which should still occur in the protected
>section (the exception handler and/or the ifAbsent-block might want to
>change the EphemeronDictionary). But shouldn't the methods
>#associations, #includesKey: and #localBindings use
>#evaluateWithFullProtection:? And why does
>WeakDictionary>>associationsDo: use "accessLock critical:" while
>EphemeronDictionary>>associationsDo: uses #evaluateWithFullProtection:?
>
>I would expect that all the query methods use
>#evaluateWithFullProtection: while all methods which might change the
>dictionary (including all methods which have a block as an argument)
>should use "accessLock critical:". Is that reasonable?
>
>I'm also wondering about WeakDictionary>>setAccessLock:. When a
>WeakDictionary is growing, it makes sure that it uses the same
>accessLock afterwards. There is no corresponding code in
>EphemeronDictionary. When it grows, it will use a new RecursionLock
>afterwards. Could this cause problems during the transition from the old
>to the new dictionary?
>
>It seems that there is a bug in EphemeronDictionary>>changeCapacityTo:
>In this method, #associationsDo: is protecting the original self, but
>the protection does not extend to the final #become:. If finalization
>happens to mourn an Ephemeron key just between copying the associations
> and the become:, then the new copy will still contain the now obsolete
>Ephemeron. If the VM does not detect that it is still there (after all,
>it believes it has already dealt with it), then the Ephemeron will hang
>around forever. I don't have the VM code to check this. The problem, if
>it actually exists, might cause memory leaks in DependentsFields,
>EventHandlers and the SubscriptionRegistry of the Announcement framework.
>
>The following code illustrates the problem:
>| dict newDict key ephemeron |
>dict := EphemeronDictionary new.
>key := String new.
>dict at: key put: 1.
>ephemeron := dict associationAt: key.
>key := nil. "dereference it, probably not necessary here"
>"simulate #changeCapacityTo:"
>newDict := dict copyEmpty: 10.
>dict associationsDo: [:each | newDict noCheckAdd: each].
>"simulate finalization during #changeCapacityTo:"
>ephemeron mourn.
>dict become: newDict.
>"see what's left"
>dict associations    "OrderedCollection (''->1) - still there"
>newDict associations "OrderedCollection () - this is the old self"
>
>Best regards,
>Joachim Geidel

--
Alan Knight [|], Cincom Smalltalk Development
[hidden email]
[hidden email]
http://www.cincom.com/smalltalk

"The Static Typing Philosophy: Make it fast. Make it right. Make it run." - Niall Ross