-
Notifications
You must be signed in to change notification settings - Fork 8
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
PropertyStateHandler dependency registration leaks memory #328
Comments
The patch looks accurate to me, @zepumph can you please review? |
This is blocking ph-scale RC, see phetsims/ph-scale#199. Milestone for 1.4 publication is ?? This is blocking natural-selection prototype RC, see phetsims/natural-selection#205. Milestone for publication is "this week if possible". |
After the below patch, I didn't see anything in my heap comparison relating to the sets in PropertyStateHandler in PhET brand. @samreid do you think it is reasonable to no-op in phet brand for this module? I think I prefer it to the above patch. Index: js/PropertyStateHandler.js
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- js/PropertyStateHandler.js (revision ebad74acf9f07d850708e39ca024f1c4e1bcc40a)
+++ js/PropertyStateHandler.js (date 1598404471186)
@@ -132,14 +132,16 @@
* @param {PropertyStatePhase} afterPhase
*/
registerPhetioOrderDependency( beforeProperty, beforePhase, afterProperty, afterPhase ) {
+ if ( Tandem.PHET_IO_ENABLED ) {
- this.validatePropertyPhasePair( beforeProperty, beforePhase );
- this.validatePropertyPhasePair( afterProperty, afterPhase );
- assert && beforeProperty === afterProperty && assert( beforePhase !== afterPhase, 'cannot set same Property to same phase' );
+ this.validatePropertyPhasePair( beforeProperty, beforePhase );
+ this.validatePropertyPhasePair( afterProperty, afterPhase );
+ assert && beforeProperty === afterProperty && assert( beforePhase !== afterPhase, 'cannot set same Property to same phase' );
- const mapPair = this.getMapPairFromPhases( beforePhase, afterPhase );
+ const mapPair = this.getMapPairFromPhases( beforePhase, afterPhase );
- mapPair.addOrderDependency( beforeProperty.tandem.phetioID, afterProperty.tandem.phetioID );
+ mapPair.addOrderDependency( beforeProperty.tandem.phetioID, afterProperty.tandem.phetioID );
+ }
}
/**
@@ -158,38 +160,40 @@
* @public
*/
unregisterOrderDependenciesForProperty( property ) {
- this.validateInstrumentedProperty( property );
+ if ( Tandem.PHET_IO_ENABLED ) {
+ this.validateInstrumentedProperty( property );
- // Be graceful if given a Property that is not registered in an order dependency.
- if ( this.propertyInAnOrderDependency( property ) ) {
- assert && assert( this.propertyInAnOrderDependency( property ), 'Property must be registered in an order dependency to be unregistered' );
+ // Be graceful if given a Property that is not registered in an order dependency.
+ if ( this.propertyInAnOrderDependency( property ) ) {
+ assert && assert( this.propertyInAnOrderDependency( property ), 'Property must be registered in an order dependency to be unregistered' );
- const phetioIDToRemove = property.tandem.phetioID;
+ const phetioIDToRemove = property.tandem.phetioID;
- this.mapPairs.forEach( mapPair => {
- [ mapPair.beforeMap, mapPair.afterMap ].forEach( map => {
- if ( map.has( phetioIDToRemove ) ) {
- map.get( phetioIDToRemove ).forEach( phetioID => {
- const setOfAfterMapIDs = map.otherMap.get( phetioID );
- setOfAfterMapIDs && setOfAfterMapIDs.delete( phetioIDToRemove );
+ this.mapPairs.forEach( mapPair => {
+ [ mapPair.beforeMap, mapPair.afterMap ].forEach( map => {
+ if ( map.has( phetioIDToRemove ) ) {
+ map.get( phetioIDToRemove ).forEach( phetioID => {
+ const setOfAfterMapIDs = map.otherMap.get( phetioID );
+ setOfAfterMapIDs && setOfAfterMapIDs.delete( phetioIDToRemove );
- // Clear out empty entries to avoid having lots of empty Sets sitting around
- setOfAfterMapIDs.size === 0 && map.otherMap.delete( phetioID );
- } );
- }
- map.delete( phetioIDToRemove );
- } );
- } );
+ // Clear out empty entries to avoid having lots of empty Sets sitting around
+ setOfAfterMapIDs.size === 0 && map.otherMap.delete( phetioID );
+ } );
+ }
+ map.delete( phetioIDToRemove );
+ } );
+ } );
- // Look through every dependency and make sure the phetioID to remove has been completely removed.
- assertSlow && this.mapPairs.forEach( mapPair => {
- [ mapPair.beforeMap, mapPair.afterMap ].forEach( map => {
- for ( const [ key, valuePhetioIDs ] of map ) {
- assertSlow && assertSlow( key !== phetioIDToRemove, 'should not be a key' );
- assertSlow && assertSlow( !valuePhetioIDs.has( phetioIDToRemove ), 'should not be in a value list' );
- }
- } );
- } );
+ // Look through every dependency and make sure the phetioID to remove has been completely removed.
+ assertSlow && this.mapPairs.forEach( mapPair => {
+ [ mapPair.beforeMap, mapPair.afterMap ].forEach( map => {
+ for ( const [ key, valuePhetioIDs ] of map ) {
+ assertSlow && assertSlow( key !== phetioIDToRemove, 'should not be a key' );
+ assertSlow && assertSlow( !valuePhetioIDs.has( phetioIDToRemove ), 'should not be in a value list' );
+ }
+ } );
+ } );
+ }
}
}
|
That looks great, I removed extraneous guards, tested in natural selection and everything looks good. Back to @zepumph for double check, then I think this issue can be closed. |
Looks great to me! I also want to note that besides |
Reopening. Have all sims that this affects been identified? Do they know which shas to cherry-pick? Do they have sim-specific issues? |
Build a molecule is currently at 1.0.0.rc.2 as of today, is there a set of shas I should cherry-pick @zepumph? This is a phet brand sim. |
Right! I think the list of sims that will need this change are:
Depending on how old shas are, likely just trying to cherry-pick e52271c will not work. Instead it will need to be a manual patch in which the entire guts of two functions, PropertyStateHandler.registerPhetioOrderDependency and unregisterOrderDependenciesForProperty are wrapped in an if statement like: if ( Tandem.PHET_IO_ENABLED ) {
. . .
} To test that this fixes the leak, you will want to do something similary to phetsims/natural-selection#189 (comment). Unfortunately I don't exactly know what the memory leak will look like with older shas, since it won't use |
I created issues in the sims that I know of that are currently in RC. @pixelzoom is there anything more that you recommend? Please review by comment above about how to cherry-pick changes. This is complicated by the large refactor done 2 weeks ago in PropertyStateHandler (memory leak was most likely present before that work also). |
First, I'd like to check whether this sim is affected by this memory leak. Self-assigning to test it out before we proceed with a maintenance release. |
@samreid which sim are you referring to? Several have been identified. |
If I understand correctly, EFAC SHAs will be taken from master in the near future, and on Slack it was agreed that ph-Scale would take SHAs from master for its next release. So I'll check BAM and SOM to see if they need this in a maintenance release. |
I tested this patch with Natural Selection in phet brand and saw that it had 20,000+ listeners in a minute or two: Index: js/PropertyStateHandler.js
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- js/PropertyStateHandler.js (revision e52271cf099a7665dbd7f293b20dd04c3e9c8159)
+++ js/PropertyStateHandler.js (date 1598483962306)
@@ -132,16 +132,18 @@
* @param {PropertyStatePhase} afterPhase
*/
registerPhetioOrderDependency( beforeProperty, beforePhase, afterProperty, afterPhase ) {
- if ( Tandem.PHET_IO_ENABLED ) {
+ // if ( Tandem.PHET_IO_ENABLED ) {
- this.validatePropertyPhasePair( beforeProperty, beforePhase );
- this.validatePropertyPhasePair( afterProperty, afterPhase );
- assert && beforeProperty === afterProperty && assert( beforePhase !== afterPhase, 'cannot set same Property to same phase' );
+ this.validatePropertyPhasePair( beforeProperty, beforePhase );
+ this.validatePropertyPhasePair( afterProperty, afterPhase );
+ assert && beforeProperty === afterProperty && assert( beforePhase !== afterPhase, 'cannot set same Property to same phase' );
- const mapPair = this.getMapPairFromPhases( beforePhase, afterPhase );
+ const mapPair = this.getMapPairFromPhases( beforePhase, afterPhase );
- mapPair.addOrderDependency( beforeProperty.tandem.phetioID, afterProperty.tandem.phetioID );
- }
+ mapPair.addOrderDependency( beforeProperty.tandem.phetioID, afterProperty.tandem.phetioID );
+
+ // console.log();
+ // }
}
/**
@@ -160,7 +162,7 @@
* @public
*/
unregisterOrderDependenciesForProperty( property ) {
- if ( Tandem.PHET_IO_ENABLED ) {
+ // if ( Tandem.PHET_IO_ENABLED ) {
this.validateInstrumentedProperty( property );
// Be graceful if given a Property that is not registered in an order dependency.
@@ -194,7 +196,7 @@
} );
} );
}
- }
+ // }
}
/**
@@ -435,12 +437,16 @@
if ( !this.beforeMap.has( beforePhetioID ) ) {
this.beforeMap.set( beforePhetioID, new Set() );
}
- this.beforeMap.get( beforePhetioID ).add( afterPhetioID );
+ const b = this.beforeMap.get( beforePhetioID );
+ b.add( afterPhetioID );
if ( !this.afterMap.has( afterPhetioID ) ) {
this.afterMap.set( afterPhetioID, new Set() );
}
- this.afterMap.get( afterPhetioID ).add( beforePhetioID );
+ const a = this.afterMap.get( afterPhetioID );
+ a.add( beforePhetioID );
+
+ console.log( a.size, b.size );
}
/**
Index: js/Property.js
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- js/Property.js (revision e52271cf099a7665dbd7f293b20dd04c3e9c8159)
+++ js/Property.js (date 1598484023766)
@@ -483,7 +483,7 @@
this.unlinkAll();
// unregister any order dependencies for this Property for PhET-iO state
- if ( this.isPhetioInstrumented() ) {
+ if ( false && this.isPhetioInstrumented() ) {
propertyStateHandlerSingleton.unregisterOrderDependenciesForProperty( this );
}
I tested the same patch in Build a Molecule fuzzing and the listeners leveled out around 2, so it did not have this same symptom. Likewise, States of Matter leveled out around 3, so it did not have this symptom. I did not take the time to run this analysis for the RC shas, but this is likely to be a good measure of the sim behavior, since the sim-specific code probably hasn't changed much since the RCs. This is more likely a problem in Natural Selection since it uses In any case, it seems like those sims don't need this in a maintenance release, especially if QA testing revealed no memory leaks. |
@pixelzoom can you please review the past 3 comments and see if this issue can be closed? |
I don't understand this patch or what you're trying to demonstrate. Are you purposely not unregistering dependencies and counting how many there are? If so, why? |
Oh I (maybe) see. Are you purposely not unregistering dependencies so that you can see how how many a sim has, and therefore whether this fix is important to that sim? |
The memory leak comes from items being added over and over to the sets in PropertyStateHandler. We saw this in Natural Selection a few days ago. I reverted the fix for this issue and confirmed I saw the problem again in Natural Selection (Sets in ProppertyStateHandler growing without bounds). With the fix still reverted, I tested BAM and SOM and saw the lists never went beyond 2 or 3 during at least minute of fuzzing. Therefore they do not exercise PropertyStateHandler in a way that produces a memory leak. Therefore they do not require this patch for correct operation as they are now. If we think it is likely that a maintenance branch of SOM or BAM will start triggering this scenario (I believe it's by disposing numerous instrumented Properties that have order dependencies), then we should apply the patch, following the instructions in #328 (comment). To me, it seems unlikely to create a maintenance release that triggers this bug, so it may not be worth the effort. I ran this test not knowing what answer I would get. If I saw a memory leak, it would have been easy to say "it's essential to maintenance release this", but since there was no leak, it probably doesn't need the patch. But the patch wouldn't hurt and wouldn't take long to apply. I don't feel strongly one way or another, just wanted to identify options, costs and risks. UPDATE: Two other angles I considered that I think I should make explicit:
|
In a related PhET-iO issue, #328 (comment), @samreid said:
Yes, that has been a concern (and recurring cost) for me since mid-July, when QA first started reporting problems for ph-scale 1.4.0-rc.1. For previous sims, it's been a non-issue. There are usually a handful of sim and common-code issues that need to be picked up in the branch, and it has been straightforward. But cherry-picking is only one of many problems that I encountered, which you can read more about in the postmortem at https://github.com/phetsims/special-ops/issues/165. |
Memory tests look good for both NS brands, see phetsims/natural-selection#189 and phetsims/natural-selection#190. Feel free to close if there's nothing else to do here. |
While working on memory leaks in phetsims/natural-selection#189, @samreid and I identified a leak in dependencies that are registered with PropertyStateHandler. We identified this by comparing heap snapshots for natural-selection:
In brand=phet, DerivedProperty registers its dependencies like this:
But Property (and therefore DerivedProperty) unregisters like this:
Note the difference in the
if
expressions. Unregistering requiresTandem.PHET_IO_ENABLED
, while registering does not. So inbrand=phet
, dependencies are registered, never unregistered, and memory leaks. (This is also the source of the string arrays of phetioIDs that were identified in phetsims/natural-selection#189 (comment).)We decided on the following patch, which needs to be reviewed by @zepumph. Like the patch we proposed for Tandem in phetsims/tandem#203, this patch proposes to remove the
Tandem.PHET_IO_ENABLED
condition.patch
Top priority, because this is blocking multiple sims.
The text was updated successfully, but these errors were encountered: