@@ -10,6 +10,7 @@ import "C"
10
10
11
11
import (
12
12
"fmt"
13
+ "log"
13
14
"runtime"
14
15
"runtime/debug"
15
16
"runtime/pprof"
@@ -24,7 +25,7 @@ import (
24
25
_ "go4.org/unsafe/assume-no-moving-gc"
25
26
)
26
27
27
- const maxTypesAllowed = 3
28
+ const maxTypesAllowed = 2
28
29
29
30
var knownTypes uint32 = 0
30
31
@@ -36,7 +37,7 @@ type BoxedType[T any] struct {
36
37
func RegisterType [T any ](ctor func (* Box ) * T ) BoxedType [T ] {
37
38
t := BoxedType [T ]{
38
39
ctor : ctor ,
39
- id : atomic .AddUint32 (& knownTypes , 1 ),
40
+ id : atomic .AddUint32 (& knownTypes , 1 ) - 1 ,
40
41
}
41
42
if t .id > maxTypesAllowed {
42
43
panic ("BoxedType ID overflow" )
@@ -81,18 +82,29 @@ func (t *BoxedType[T]) Delete(box *Box) {
81
82
82
83
// Box is an opaque type holding extra data.
83
84
type Box struct {
84
- dummy * unsafe.Pointer
85
- data [maxTypesAllowed ]unsafe.Pointer
85
+ gobject unsafe.Pointer
86
+ dummy * boxDummy
87
+ data [maxTypesAllowed ]unsafe.Pointer
88
+ }
89
+
90
+ type boxDummy struct {
91
+ gobject unsafe.Pointer
86
92
}
87
93
88
94
// Object returns Box's C GObject pointer.
89
95
func (b * Box ) GObject () unsafe.Pointer {
90
- return atomic . LoadPointer ( & b . data [ 0 ])
96
+ return b . gobject
91
97
}
92
98
93
99
// Hack to force an object on the heap.
94
100
var never bool
95
- var sink interface {}
101
+ var sink_ interface {}
102
+
103
+ func sink (v interface {}) {
104
+ if never {
105
+ sink_ = v
106
+ }
107
+ }
96
108
97
109
var (
98
110
traceObjects = gdebug .NewDebugLoggerNullable ("trace-objects" )
@@ -113,11 +125,12 @@ func objInfo(obj unsafe.Pointer) string {
113
125
// newBox creates a zero-value instance of Box.
114
126
func newBox (obj unsafe.Pointer ) * Box {
115
127
box := & Box {}
116
- box .data [ 0 ] = obj
128
+ box .gobject = obj
117
129
118
130
// Cheat Go's GC by adding a finalizer to a dummy pointer that is inside Box
119
131
// but is not Box itself.
120
- box .dummy = & obj
132
+ box .dummy = & boxDummy {gobject : obj }
133
+ sink (box .dummy )
121
134
runtime .SetFinalizer (box .dummy , finalizeBox )
122
135
123
136
if objectProfile != nil {
@@ -131,9 +144,7 @@ func newBox(obj unsafe.Pointer) *Box {
131
144
// Force box on the heap. Objects on the stack can move, but not objects on
132
145
// the heap. At least not for now; the assume-no-moving-gc import will
133
146
// guard against that.
134
- if never {
135
- sink = box
136
- }
147
+ sink (box )
137
148
138
149
return box
139
150
}
@@ -170,10 +181,7 @@ func Get(gobject unsafe.Pointer, take bool) *Box {
170
181
// If the registry is currently strongly referenced, then we must move it to
171
182
// a weak reference.
172
183
173
- shared .mu .RLock ()
174
- box , _ := gets (gobject )
175
- shared .mu .RUnlock ()
176
-
184
+ box := TryGet (gobject )
177
185
if box != nil {
178
186
return box
179
187
}
@@ -196,14 +204,14 @@ func Get(gobject unsafe.Pointer, take bool) *Box {
196
204
//
197
205
shared .strong [gobject ] = box
198
206
199
- shared .mu .Unlock ()
200
-
201
207
if toggleRefs != nil {
202
208
toggleRefs .Println (objInfo (gobject ),
203
209
"Get: will introduce new box, current ref =" ,
204
210
C .g_atomic_int_get ((* C .gint )(unsafe .Pointer (& (* C .GObject )(gobject ).ref_count ))))
205
211
}
206
212
213
+ shared .mu .Unlock ()
214
+
207
215
C .g_object_add_toggle_ref (
208
216
(* C .GObject )(gobject ),
209
217
(* [0 ]byte )(C .goToggleNotify ), nil ,
@@ -236,132 +244,71 @@ func Get(gobject unsafe.Pointer, take bool) *Box {
236
244
237
245
// Free explicitly frees the box permanently. It must not be resurrected after
238
246
// this.
247
+ //
248
+ // Deprecated: this function is no longer needed.
239
249
func Free (box * Box ) {
240
- obj := box .GObject ()
241
- if obj == nil {
242
- panic ("bug: Free called on already freed object" )
243
- }
244
-
245
- shared .mu .Lock ()
246
- delete (shared .strong , obj )
247
- delete (shared .weak , obj )
248
- for i := range box .data {
249
- atomic .StorePointer (& box .data [i ], nil )
250
- }
251
- shared .mu .Unlock ()
252
-
253
- C .g_object_remove_toggle_ref (
254
- (* C .GObject )(unsafe .Pointer (obj )),
255
- (* [0 ]byte )(C .goToggleNotify ), nil ,
256
- )
257
-
258
- if toggleRefs != nil {
259
- toggleRefs .Println (objInfo (obj ), "Free: explicitly removed toggle ref" )
260
- }
261
-
262
- if objectProfile != nil {
263
- objectProfile .Remove (obj )
264
- }
250
+ panic ("not implemented" )
265
251
}
266
252
267
- var finalizing atomic.Bool
268
-
269
253
// finalizeBox only delays its finalization until GLib notifies us a toggle. It
270
254
// does so for as long as an object is stored only in the Go heap. Once the
271
255
// object is also shared, the toggle notifier will strongly reference the Box.
272
- func finalizeBox (dummy * unsafe.Pointer ) {
273
- // obj := box.GObject()
274
- // if obj == nil {
275
- // return
276
- // }
277
-
278
- // var objInfoRes string
279
- // if toggleRefs != nil {
280
- // objInfoRes = objInfo(obj)
281
- // toggleRefs.Println(objInfoRes, "finalizeBox: acquiring lock...")
282
- // }
256
+ func finalizeBox (dummy * boxDummy ) {
257
+ if dummy == nil {
258
+ panic ("bug: finalizeBox called with nil dummy" )
259
+ }
283
260
284
261
shared .mu .Lock ()
285
262
286
- if ! finalizing .CompareAndSwap (false , true ) {
287
- panic ("impossible: finalizeBox called while finalizing" )
288
- }
289
- defer finalizing .Store (false )
290
-
291
- box , _ := gets (* dummy )
263
+ box , strong := gets (dummy .gobject )
292
264
if box == nil {
293
- panic ("bug: finalizeBox called on already freed object" )
265
+ log .Print ("gotk4: intern: finalizer got unknown gobject " , dummy .gobject , ", ignoring" )
266
+ shared .mu .Unlock ()
267
+ return
294
268
}
295
269
296
- obj := box .GObject ()
297
-
298
270
var objInfoRes string
299
271
if toggleRefs != nil {
300
- objInfoRes = objInfo (obj )
272
+ objInfoRes = objInfo (dummy . gobject )
301
273
toggleRefs .Println (objInfoRes , "finalizeBox: acquiring lock..." )
302
274
}
303
275
304
- if ! freeBox (box ) {
276
+ if strong {
277
+ // If the closures are strong-referenced, then they might still be
278
+ // referenced from the C side, and those closures might access this
279
+ // object. Don't free.
280
+
305
281
// Delegate finalizing to the next cycle.
306
- runtime .SetFinalizer (box .dummy , finalizeBox )
282
+ runtime .SetFinalizer (dummy , finalizeBox )
283
+
307
284
shared .mu .Unlock ()
308
285
309
286
if toggleRefs != nil {
310
287
toggleRefs .Println (objInfoRes , "finalizeBox: moving finalize to next GC cycle" )
311
288
}
312
289
} else {
290
+ // If the closures are weak-referenced, then the object reference hasn't
291
+ // been toggled yet. Since the object is going away and we're still
292
+ // weakly referenced, we can wipe the closures away.
293
+ delete (shared .weak , dummy .gobject )
294
+
295
+ shared .mu .Unlock ()
296
+
313
297
// Unreference the object. This will potentially free the object as
314
298
// well. The closures are definitely gone at this point.
315
299
C .g_object_remove_toggle_ref (
316
- (* C .GObject )(unsafe .Pointer (obj )),
300
+ (* C .GObject )(unsafe .Pointer (dummy . gobject )),
317
301
(* [0 ]byte )(C .goToggleNotify ), nil ,
318
302
)
319
- shared .mu .Unlock ()
320
303
321
304
if toggleRefs != nil {
322
305
toggleRefs .Println (objInfoRes , "finalizeBox: removed toggle ref during GC" )
323
306
}
324
307
325
308
if objectProfile != nil {
326
- objectProfile .Remove (obj )
327
- }
328
- }
329
- }
330
-
331
- // freeBox must only be called during finalizing of a box. It's used to know if
332
- // a box should be freed or not during finalization. If false is returned, then
333
- // the object must not be freed yet.
334
- //
335
- //go:nocheckptr
336
- func freeBox (box * Box ) bool {
337
- b , ok := shared .strong [box .GObject ()]
338
- if ok {
339
- if b != box {
340
- panic ("bug: multiple Box found for same GObject" )
341
- }
342
- // If the closures are strong-referenced, then they might still be
343
- // referenced from the C side, and those closures might access this
344
- // object. Don't free.
345
- return false
346
- }
347
-
348
- _ , ok = shared .weak [box .GObject ()]
349
- if ok {
350
- // If the closures are weak-referenced, then the object reference hasn't
351
- // been toggled yet. Since the object is going away and we're still
352
- // weakly referenced, we can wipe the closures away.
353
- delete (shared .weak , box .GObject ())
354
-
355
- // By setting *box to a zero-value of closures, we're nilling out the
356
- // maps, which will signal to Go that these cyclical objects can be
357
- // freed altogether.
358
- for i := range box .data {
359
- atomic .StorePointer (& box .data [i ], nil )
309
+ objectProfile .Remove (dummy .gobject )
360
310
}
361
311
}
362
-
363
- // We can proceed to free the object.
364
- return true
365
312
}
366
313
367
314
// goToggleNotify is called by GLib on each toggle notification. It doesn't
@@ -373,10 +320,7 @@ func goToggleNotify(_ C.gpointer, obj *C.GObject, isLastInt C.gboolean) {
373
320
gobject := unsafe .Pointer (obj )
374
321
isLast := isLastInt != C .FALSE
375
322
376
- if ! finalizing .Load () {
377
- shared .mu .Lock ()
378
- defer shared .mu .Unlock ()
379
- }
323
+ shared .mu .Lock ()
380
324
381
325
if isLast {
382
326
// delete(shared.sharing, gobject)
@@ -386,6 +330,8 @@ func goToggleNotify(_ C.gpointer, obj *C.GObject, isLastInt C.gboolean) {
386
330
makeStrong (gobject )
387
331
}
388
332
333
+ shared .mu .Unlock ()
334
+
389
335
if toggleRefs != nil {
390
336
toggleRefs .Println (objInfo (unsafe .Pointer (obj )), "goToggleNotify: is last =" , isLast )
391
337
}
0 commit comments