@@ -34,17 +34,18 @@ type Value = { [key: string]: any };
34
34
// backend project, and not by frontend browser clients.
35
35
// The garbage collection is deleting models and related
36
36
// data when they are not referenced in the notebook.
37
+ // Also, we don't implement complete object delete yet so instead we
38
+ // set the data field to null, which clears all state about and
39
+ // object and makes it easy to know to ignore it.
37
40
const GC_DEBOUNCE_MS = 10000 ;
38
41
39
- // actually doing GC is bonus points; I don't think upstream even does it,
40
- // and it seems very hard to get right without causing subtle bugs. We disable
41
- // it for now. I think this core example breaks it, because the link isn't
42
- // taken into account properly:
43
- // from ipywidgets import VBox, jsdlink, IntSlider, Button
44
- // s1 = IntSlider(max=200, value=100); s2 = IntSlider(value=40)
45
- // jsdlink((s1, 'value'), (s2, 'max'))
46
- // VBox([s1, s2])
47
- const DISABLE_GC = true ;
42
+ // If for some reason GC needs to be deleted, e.g., maybe you
43
+ // suspect a bug, just toggle this flag. In particular, note
44
+ // includeThirdPartyReferences below that has to deal with a special
45
+ // case schema that k3d uses for references, which they just made up,
46
+ // which works with official upstream, since that has no garbage
47
+ // collection.
48
+ const DISABLE_GC = false ;
48
49
49
50
interface CommMessage {
50
51
header : { msg_id : string } ;
@@ -168,7 +169,9 @@ export class IpywidgetsState extends EventEmitter {
168
169
169
170
// assembles together state we know about the widget with given model_id
170
171
// from info in the table, and returns it as a Javascript object.
171
- getSerializedModelState = ( model_id : string ) : SerializedModelState | undefined => {
172
+ getSerializedModelState = (
173
+ model_id : string ,
174
+ ) : SerializedModelState | undefined => {
172
175
this . assert_state ( "ready" ) ;
173
176
const state = this . get ( model_id , "state" ) ;
174
177
if ( state == null ) {
@@ -379,7 +382,7 @@ export class IpywidgetsState extends EventEmitter {
379
382
// new ones come or current ones change. With shallow merge,
380
383
// the existing ones go away, which is very broken, e.g.,
381
384
// see this with this example:
382
- /*
385
+ /*
383
386
import bqplot.pyplot as plt
384
387
import numpy as np
385
388
x, y = np.random.rand(2, 10)
@@ -466,7 +469,10 @@ scat.x, scat.y = np.random.rand(2, 50)
466
469
// which is why we just set the data to null.
467
470
const activeIds = this . getActiveModelIds ( ) ;
468
471
this . table . get ( ) ?. forEach ( ( val , key ) => {
469
- if ( key == null || val == null || val . get ( "data" ) == null ) return ; // already deleted
472
+ if ( key == null || val ?. get ( "data" ) == null ) {
473
+ // already deleted
474
+ return ;
475
+ }
470
476
const [ string_id , model_id , type ] = JSON . parse ( key ) ;
471
477
if ( ! activeIds . has ( model_id ) ) {
472
478
this . table . set (
@@ -501,9 +507,12 @@ scat.x, scat.y = np.random.rand(2, 50)
501
507
}
502
508
after = modelIds . size ;
503
509
}
504
- // Also any custom ways of doing referencing.. .
510
+ // Also any custom ways of doing referencing -- e.g., k3d does this .
505
511
this . includeThirdPartyReferences ( modelIds ) ;
506
512
513
+ // Also anything that references any modelIds
514
+ this . includeReferenceTo ( modelIds ) ;
515
+
507
516
return modelIds ;
508
517
} ;
509
518
@@ -534,6 +543,30 @@ scat.x, scat.y = np.random.rand(2, 50)
534
543
return this . getReferencedModelIds ( modelIds ) ;
535
544
} ;
536
545
546
+ private includeReferenceTo = ( modelIds : Set < string > ) => {
547
+ // This example is extra tricky and one version of our GC broke it:
548
+ // from ipywidgets import VBox, jsdlink, IntSlider, Button; s1 = IntSlider(max=200, value=100); s2 = IntSlider(value=40); jsdlink((s1, 'value'), (s2, 'max')); VBox([s1, s2])
549
+ // What happens here is that this jsdlink model ends up referencing live widgets,
550
+ // but is not referenced by any cell, so it would get garbage collected.
551
+
552
+ let before = - 1 ;
553
+ let after = modelIds . size ;
554
+ while ( before < after ) {
555
+ before = modelIds . size ;
556
+ this . table . get ( ) ?. forEach ( ( val ) => {
557
+ const data = val ?. get ( "data" ) ;
558
+ if ( data != null ) {
559
+ for ( const model_id of getModelIds ( data ) ) {
560
+ if ( modelIds . has ( model_id ) ) {
561
+ modelIds . add ( val . get ( "model_id" ) ) ;
562
+ }
563
+ }
564
+ }
565
+ } ) ;
566
+ after = modelIds . size ;
567
+ }
568
+ } ;
569
+
537
570
private includeThirdPartyReferences = ( modelIds : Set < string > ) => {
538
571
/*
539
572
Motivation (RANT):
0 commit comments