From b1e47e0e1688ae6d65e57bb5486d934b94db03b3 Mon Sep 17 00:00:00 2001 From: Max Leske Date: Sun, 26 Nov 2023 09:59:32 +0100 Subject: [PATCH] Implement optimized ephemeron support --- .../FLEphemeronTest.class/README.md | 0 ...AssociationMourningAfterMaterialization.st | 24 ++++++++++++++ ...ssociationMourningBeforeMaterialization.st | 29 +++++++++++++++++ ...yAssociationMourningBeforeSerialization.st | 17 ++++++++++ ...stWeakKeyAssociationWithStrongReference.st | 29 +++++++++++++++++ ...eyAssociationWithStrongReferenceInGraph.st | 32 +++++++++++++++++++ .../FLEphemeronTest.class/properties.json | 11 +++++++ .../instance/hasRealAssociations..st | 11 +++---- .../instance/referencesOf.do..st | 2 -- .../properties.json | 2 +- .../FLEphemeronCluster.class/README.md | 5 +++ .../instance/clusterReferencesDo..st | 6 ++++ .../instance/referencesOf.do..st | 12 +++++++ .../instance/serializeReferencesOf.with..st | 10 ++++++ .../FLEphemeronCluster.class/properties.json | 11 +++++++ .../instance/visitEphemeron..st | 4 +++ .../README.md | 2 +- .../properties.json | 2 +- 18 files changed, 198 insertions(+), 11 deletions(-) create mode 100644 repository/Fuel-Core-Tests.package/FLEphemeronTest.class/README.md create mode 100644 repository/Fuel-Core-Tests.package/FLEphemeronTest.class/instance/testWeakKeyAssociationMourningAfterMaterialization.st create mode 100644 repository/Fuel-Core-Tests.package/FLEphemeronTest.class/instance/testWeakKeyAssociationMourningBeforeMaterialization.st create mode 100644 repository/Fuel-Core-Tests.package/FLEphemeronTest.class/instance/testWeakKeyAssociationMourningBeforeSerialization.st create mode 100644 repository/Fuel-Core-Tests.package/FLEphemeronTest.class/instance/testWeakKeyAssociationWithStrongReference.st create mode 100644 repository/Fuel-Core-Tests.package/FLEphemeronTest.class/instance/testWeakKeyAssociationWithStrongReferenceInGraph.st create mode 100644 repository/Fuel-Core-Tests.package/FLEphemeronTest.class/properties.json create mode 100644 repository/Fuel-Core.package/FLEphemeronCluster.class/README.md create mode 100644 repository/Fuel-Core.package/FLEphemeronCluster.class/instance/clusterReferencesDo..st create mode 100644 repository/Fuel-Core.package/FLEphemeronCluster.class/instance/referencesOf.do..st create mode 100644 repository/Fuel-Core.package/FLEphemeronCluster.class/instance/serializeReferencesOf.with..st create mode 100644 repository/Fuel-Core.package/FLEphemeronCluster.class/properties.json create mode 100644 repository/Fuel-Core.package/FLLightGeneralMapper.class/instance/visitEphemeron..st diff --git a/repository/Fuel-Core-Tests.package/FLEphemeronTest.class/README.md b/repository/Fuel-Core-Tests.package/FLEphemeronTest.class/README.md new file mode 100644 index 00000000..e69de29b diff --git a/repository/Fuel-Core-Tests.package/FLEphemeronTest.class/instance/testWeakKeyAssociationMourningAfterMaterialization.st b/repository/Fuel-Core-Tests.package/FLEphemeronTest.class/instance/testWeakKeyAssociationMourningAfterMaterialization.st new file mode 100644 index 00000000..e403f9df --- /dev/null +++ b/repository/Fuel-Core-Tests.package/FLEphemeronTest.class/instance/testWeakKeyAssociationMourningAfterMaterialization.st @@ -0,0 +1,24 @@ +tests +testWeakKeyAssociationMourningAfterMaterialization + "This tests serialization / materialization of WeakKeyAssociation, which is an ephemeron class. + When the key is not strongly referenced, which is the case here, the garbage collector + will send #mourn to the object to finalize it. This will cause the ephemeron to remove itself + from its container." + + | dictionary ephemeron materializedDictionary materializedAssociation | + dictionary := Dictionary new. + ephemeron := WeakKeyAssociation new + key: Object new; + value: 'value'; + container: dictionary. + dictionary add: ephemeron. + + materializedDictionary := self resultOfSerializeAndMaterialize: dictionary. + self deny: materializedDictionary isEmpty. + self assert: materializedDictionary size equals: 1. + materializedAssociation := materializedDictionary associations first. + self assert: materializedAssociation key isNil. + self assert: materializedAssociation value equals: 'value'. + + Smalltalk garbageCollect. + self deny: materializedDictionary isEmpty \ No newline at end of file diff --git a/repository/Fuel-Core-Tests.package/FLEphemeronTest.class/instance/testWeakKeyAssociationMourningBeforeMaterialization.st b/repository/Fuel-Core-Tests.package/FLEphemeronTest.class/instance/testWeakKeyAssociationMourningBeforeMaterialization.st new file mode 100644 index 00000000..8e1d2db7 --- /dev/null +++ b/repository/Fuel-Core-Tests.package/FLEphemeronTest.class/instance/testWeakKeyAssociationMourningBeforeMaterialization.st @@ -0,0 +1,29 @@ +tests +testWeakKeyAssociationMourningBeforeMaterialization + "This tests serialization / materialization of WeakKeyAssociation, which is an ephemeron class. + When the key is not strongly referenced, which is the case here, the garbage collector + will send #mourn to the object to finalize it. This will cause the ephemeron to remove itself + from its container." + + | dictionary ephemeron materializedDictionary | + dictionary := Dictionary new. + ephemeron := WeakKeyAssociation new + key: Object new; + value: 'value'; + container: dictionary. + dictionary add: ephemeron. + + self deny: dictionary isEmpty. + + self serialize: dictionary. + Smalltalk garbageCollect. + self assert: dictionary isEmpty. + + materializedDictionary := self materialized. + self deny: materializedDictionary isEmpty. + self assert: materializedDictionary size equals: 1. + self assert: materializedDictionary associations first key isNil. + self assert: materializedDictionary associations first value equals: 'value'. + + Smalltalk garbageCollect. + self deny: materializedDictionary isEmpty \ No newline at end of file diff --git a/repository/Fuel-Core-Tests.package/FLEphemeronTest.class/instance/testWeakKeyAssociationMourningBeforeSerialization.st b/repository/Fuel-Core-Tests.package/FLEphemeronTest.class/instance/testWeakKeyAssociationMourningBeforeSerialization.st new file mode 100644 index 00000000..f7e01ec1 --- /dev/null +++ b/repository/Fuel-Core-Tests.package/FLEphemeronTest.class/instance/testWeakKeyAssociationMourningBeforeSerialization.st @@ -0,0 +1,17 @@ +tests +testWeakKeyAssociationMourningBeforeSerialization + "This tests serialization / materialization of WeakKeyAssociation, which is an ephemeron class. + When the key is not strongly referenced, which is the case here, the garbage collector + will send #mourn to the object to finalize it. This will cause the ephemeron to remove itself + from its container." + + | dictionary ephemeron | + dictionary := Dictionary new. + ephemeron := WeakKeyAssociation new + key: Object new; + value: 'value'; + container: dictionary. + dictionary add: ephemeron. + + Smalltalk garbageCollect. + self assert: dictionary isEmpty \ No newline at end of file diff --git a/repository/Fuel-Core-Tests.package/FLEphemeronTest.class/instance/testWeakKeyAssociationWithStrongReference.st b/repository/Fuel-Core-Tests.package/FLEphemeronTest.class/instance/testWeakKeyAssociationWithStrongReference.st new file mode 100644 index 00000000..208a4528 --- /dev/null +++ b/repository/Fuel-Core-Tests.package/FLEphemeronTest.class/instance/testWeakKeyAssociationWithStrongReference.st @@ -0,0 +1,29 @@ +tests +testWeakKeyAssociationWithStrongReference + "This tests serialization / materialization of WeakKeyAssociation, which is an ephemeron class. + When the key is strongly referenced, which is the case here, the garbage collector will + not finalize the ephemeron." + + | dictionary key ephemeron materializedDictionary | + dictionary := Dictionary new. + key := Object new. + ephemeron := WeakKeyAssociation new + key: key; + value: 'value'; + container: dictionary. + dictionary add: ephemeron. + + self deny: dictionary isEmpty. + + self serialize: dictionary. + Smalltalk garbageCollect. + self deny: dictionary isEmpty. + + materializedDictionary := self materialized. + self deny: materializedDictionary isEmpty. + self assert: materializedDictionary size equals: 1. + self assert: materializedDictionary associations first key isNil. + self assert: materializedDictionary associations first value equals: 'value'. + + Smalltalk garbageCollect. + self deny: materializedDictionary isEmpty \ No newline at end of file diff --git a/repository/Fuel-Core-Tests.package/FLEphemeronTest.class/instance/testWeakKeyAssociationWithStrongReferenceInGraph.st b/repository/Fuel-Core-Tests.package/FLEphemeronTest.class/instance/testWeakKeyAssociationWithStrongReferenceInGraph.st new file mode 100644 index 00000000..650811c3 --- /dev/null +++ b/repository/Fuel-Core-Tests.package/FLEphemeronTest.class/instance/testWeakKeyAssociationWithStrongReferenceInGraph.st @@ -0,0 +1,32 @@ +tests +testWeakKeyAssociationWithStrongReferenceInGraph + "This tests serialization / materialization of WeakKeyAssociation, which is an ephemeron class. + When the key is strongly referenced, which is the case here, the garbage collector will + not finalize the ephemeron. + If the ephemeron's key is strongly referenced from inside the graph the key should also + be serialized." + + | dictionary ephemeron materializedDictionary | + dictionary := Dictionary new. + ephemeron := WeakKeyAssociation new + key: Object new; + value: 'value'; + container: dictionary. + dictionary add: ephemeron. + + self deny: dictionary isEmpty. + + self serialize: {dictionary. ephemeron key}. + Smalltalk garbageCollect. + self deny: dictionary isEmpty. + + materializedDictionary := self materialized first. + self deny: materializedDictionary isEmpty. + self assert: materializedDictionary size equals: 1. + self assert: materializedDictionary associations first key class identicalTo: Object. + self assert: materializedDictionary associations first value equals: 'value'. + + Smalltalk garbageCollect. + "As there's no strong reference to the materialized key, the ephemeron should + be finalized by the garbage collector and remove itself from its container." + self assert: materializedDictionary isEmpty \ No newline at end of file diff --git a/repository/Fuel-Core-Tests.package/FLEphemeronTest.class/properties.json b/repository/Fuel-Core-Tests.package/FLEphemeronTest.class/properties.json new file mode 100644 index 00000000..32115faf --- /dev/null +++ b/repository/Fuel-Core-Tests.package/FLEphemeronTest.class/properties.json @@ -0,0 +1,11 @@ +{ + "commentStamp" : "", + "super" : "FLSerializationTest", + "category" : "Fuel-Core-Tests-Base", + "classinstvars" : [ ], + "pools" : [ ], + "classvars" : [ ], + "instvars" : [ ], + "name" : "FLEphemeronTest", + "type" : "normal" +} \ No newline at end of file diff --git a/repository/Fuel-Core.package/FLDictionaryCollectionCluster.class/instance/hasRealAssociations..st b/repository/Fuel-Core.package/FLDictionaryCollectionCluster.class/instance/hasRealAssociations..st index 292378b5..24275521 100644 --- a/repository/Fuel-Core.package/FLDictionaryCollectionCluster.class/instance/hasRealAssociations..st +++ b/repository/Fuel-Core.package/FLDictionaryCollectionCluster.class/instance/hasRealAssociations..st @@ -1,12 +1,11 @@ testing hasRealAssociations: aDictionary - "Only check the first association, assume that all other entries + "Weak assocations and ephemerons should not be serialized as simple key / value tuples + but with their actual class. + + Only check the first association, assume that all other entries are of the same type" aDictionary associationsDo: [ :assoc | - ^ #( - Association - WeakKeyAssociation - WeakValueAssociation - ) includes: assoc className ]. + ^ assoc class == Association ]. ^ true \ No newline at end of file diff --git a/repository/Fuel-Core.package/FLDictionaryCollectionCluster.class/instance/referencesOf.do..st b/repository/Fuel-Core.package/FLDictionaryCollectionCluster.class/instance/referencesOf.do..st index 820f3bf8..db4c07f8 100644 --- a/repository/Fuel-Core.package/FLDictionaryCollectionCluster.class/instance/referencesOf.do..st +++ b/repository/Fuel-Core.package/FLDictionaryCollectionCluster.class/instance/referencesOf.do..st @@ -1,7 +1,5 @@ analyzing referencesOf: aDictionary do: aBlock - "In Pharo 9 the class pool is a dictionary containing ClassVariables - pretending to be Associations. For that case we need to skip the optimization." (self hasRealAssociations: aDictionary) ifTrue: [ aDictionary keysAndValuesDo: [ :key :value | diff --git a/repository/Fuel-Core.package/FLDictionaryCollectionCluster.class/properties.json b/repository/Fuel-Core.package/FLDictionaryCollectionCluster.class/properties.json index 672b5fe5..853a0ee5 100644 --- a/repository/Fuel-Core.package/FLDictionaryCollectionCluster.class/properties.json +++ b/repository/Fuel-Core.package/FLDictionaryCollectionCluster.class/properties.json @@ -1,5 +1,5 @@ { - "commentStamp" : "MaxLeske 6/2/2021 13:40", + "commentStamp" : "", "super" : "FLAbstractCollectionCluster", "category" : "Fuel-Core-Clusters-Optimized", "classinstvars" : [ ], diff --git a/repository/Fuel-Core.package/FLEphemeronCluster.class/README.md b/repository/Fuel-Core.package/FLEphemeronCluster.class/README.md new file mode 100644 index 00000000..96097298 --- /dev/null +++ b/repository/Fuel-Core.package/FLEphemeronCluster.class/README.md @@ -0,0 +1,5 @@ +I am responsible for serializing and materializing ephemerons. Ephemerons are subclasses of Association with the EphemeronLayout class layout. The garbage collector finalizes ephemerons (called "mourning") when no strong references to the key of the association remain. + +See the class comment of FinalizationRegistryEntry for additional information. + +I only serialize the ephemeron's key if there's also a strong reference to it from within the graph, as otherwise the garbage collector would finalize and collect the ephemeron immediately during materialization. \ No newline at end of file diff --git a/repository/Fuel-Core.package/FLEphemeronCluster.class/instance/clusterReferencesDo..st b/repository/Fuel-Core.package/FLEphemeronCluster.class/instance/clusterReferencesDo..st new file mode 100644 index 00000000..aab3a805 --- /dev/null +++ b/repository/Fuel-Core.package/FLEphemeronCluster.class/instance/clusterReferencesDo..st @@ -0,0 +1,6 @@ +analyzing +clusterReferencesDo: aBlock + "Ensures that nil could be encoded, later in references step." + + super clusterReferencesDo: aBlock. + aBlock value: nil \ No newline at end of file diff --git a/repository/Fuel-Core.package/FLEphemeronCluster.class/instance/referencesOf.do..st b/repository/Fuel-Core.package/FLEphemeronCluster.class/instance/referencesOf.do..st new file mode 100644 index 00000000..18f6c9c1 --- /dev/null +++ b/repository/Fuel-Core.package/FLEphemeronCluster.class/instance/referencesOf.do..st @@ -0,0 +1,12 @@ +analyzing +referencesOf: anObject do: aBlock + "Do not store strong reference to the key, which is essentially a weak reference. + Do not trace the key either. We only want to serialize the key if there's an + explicit reference to it in the graph." + | key | + key := anObject key. + variablesMapping + referencesOf: anObject + do: [ :object | + object == key ifFalse: [ + aBlock value: object ] ] \ No newline at end of file diff --git a/repository/Fuel-Core.package/FLEphemeronCluster.class/instance/serializeReferencesOf.with..st b/repository/Fuel-Core.package/FLEphemeronCluster.class/instance/serializeReferencesOf.with..st new file mode 100644 index 00000000..daabe96e --- /dev/null +++ b/repository/Fuel-Core.package/FLEphemeronCluster.class/instance/serializeReferencesOf.with..st @@ -0,0 +1,10 @@ +serialize/materialize +serializeReferencesOf: anObject with: anEncoder + "As we did not trace the key we can simply encode it here, + provided it was strongly referenced from within the graph." + anEncoder encodeWeakReferenceTo: anObject key. + + "The references do not contain the key" + super + serializeReferencesOf: anObject + with: anEncoder \ No newline at end of file diff --git a/repository/Fuel-Core.package/FLEphemeronCluster.class/properties.json b/repository/Fuel-Core.package/FLEphemeronCluster.class/properties.json new file mode 100644 index 00000000..1ecf6504 --- /dev/null +++ b/repository/Fuel-Core.package/FLEphemeronCluster.class/properties.json @@ -0,0 +1,11 @@ +{ + "commentStamp" : "MaxLeske 11/26/2023 09:58", + "super" : "FLFixedObjectCluster", + "category" : "Fuel-Core-Clusters", + "classinstvars" : [ ], + "pools" : [ ], + "classvars" : [ ], + "instvars" : [ ], + "name" : "FLEphemeronCluster", + "type" : "normal" +} \ No newline at end of file diff --git a/repository/Fuel-Core.package/FLLightGeneralMapper.class/instance/visitEphemeron..st b/repository/Fuel-Core.package/FLLightGeneralMapper.class/instance/visitEphemeron..st new file mode 100644 index 00000000..21dfadb7 --- /dev/null +++ b/repository/Fuel-Core.package/FLLightGeneralMapper.class/instance/visitEphemeron..st @@ -0,0 +1,4 @@ +visiting +visitEphemeron: anObject + + self mapAndTraceByObjectClass: anObject to: FLEphemeronCluster \ No newline at end of file diff --git a/repository/Fuel-Core.package/FLWeakVariableObjectCluster.class/README.md b/repository/Fuel-Core.package/FLWeakVariableObjectCluster.class/README.md index 674df894..1b86e663 100644 --- a/repository/Fuel-Core.package/FLWeakVariableObjectCluster.class/README.md +++ b/repository/Fuel-Core.package/FLWeakVariableObjectCluster.class/README.md @@ -1 +1 @@ -I am a cluster for objects with weak indexable variables. \ No newline at end of file +I am a cluster for objects with weak indexable variables. I only serialize a weak reference if there is also a strong reference from within the graph, as otherwise the garbage collector would immediately collect the object during materialization. \ No newline at end of file diff --git a/repository/Fuel-Core.package/FLWeakVariableObjectCluster.class/properties.json b/repository/Fuel-Core.package/FLWeakVariableObjectCluster.class/properties.json index 05e809e3..d6c699f4 100644 --- a/repository/Fuel-Core.package/FLWeakVariableObjectCluster.class/properties.json +++ b/repository/Fuel-Core.package/FLWeakVariableObjectCluster.class/properties.json @@ -1,5 +1,5 @@ { - "commentStamp" : "", + "commentStamp" : "MaxLeske 11/26/2023 09:57", "super" : "FLVariableObjectCluster", "category" : "Fuel-Core-Clusters", "classinstvars" : [ ],