|
2 | 2 | package intern
|
3 | 3 |
|
4 | 4 | // #cgo pkg-config: gobject-2.0
|
5 |
| -// #include <glib-object.h> |
6 |
| -// |
7 |
| -// extern void goToggleNotify(gpointer, GObject*, gboolean); |
8 |
| -// static const gchar* gotk4_object_type_name(gpointer obj) { return G_OBJECT_TYPE_NAME(obj); }; |
9 |
| -// |
10 |
| -// gboolean gotk4_intern_remove_toggle_ref(gpointer obj) { |
11 |
| -// g_object_remove_toggle_ref(G_OBJECT(obj), (GToggleNotify)goToggleNotify, NULL); |
12 |
| -// } |
| 5 | +// #include "intern.h" |
13 | 6 | import "C"
|
14 | 7 |
|
15 | 8 | import (
|
@@ -89,6 +82,7 @@ type Box struct {
|
89 | 82 | gobject unsafe.Pointer
|
90 | 83 | dummy *boxDummy
|
91 | 84 | data [maxTypesAllowed]unsafe.Pointer
|
| 85 | + done bool |
92 | 86 | }
|
93 | 87 |
|
94 | 88 | type boxDummy struct {
|
@@ -178,7 +172,14 @@ var shared = struct {
|
178 | 172 | //go:nosplit
|
179 | 173 | func TryGet(gobject unsafe.Pointer) *Box {
|
180 | 174 | shared.mu.RLock()
|
| 175 | + |
181 | 176 | box, _ := gets(gobject)
|
| 177 | + if box != nil && box.done { |
| 178 | + log.Panicf( |
| 179 | + "gotk4: critical: %s TryGet called on a finalized object", |
| 180 | + objInfo(gobject)) |
| 181 | + } |
| 182 | + |
182 | 183 | shared.mu.RUnlock()
|
183 | 184 | return box
|
184 | 185 | }
|
@@ -285,64 +286,57 @@ func finalizeBox(dummy *boxDummy) {
|
285 | 286 | }
|
286 | 287 |
|
287 | 288 | shared.mu.Lock()
|
| 289 | + defer shared.mu.Unlock() |
288 | 290 |
|
289 | 291 | box, strong := gets(dummy.gobject)
|
290 | 292 | if box == nil {
|
291 | 293 | log.Print("gotk4: intern: finalizer got unknown gobject ", dummy.gobject, ", ignoring")
|
292 |
| - shared.mu.Unlock() |
293 | 294 | return
|
294 | 295 | }
|
295 | 296 |
|
296 |
| - var objInfoRes string |
297 |
| - if toggleRefs != nil { |
298 |
| - objInfoRes = objInfo(dummy.gobject) |
299 |
| - toggleRefs.Println(objInfoRes, "finalizeBox: acquiring lock...") |
300 |
| - } |
| 297 | + // Always delegate the finalization to the next cycle. |
| 298 | + // This won't be the case once goFinishRemovingToggleRef is called. |
| 299 | + runtime.SetFinalizer(dummy, finalizeBox) |
301 | 300 |
|
302 |
| - if strong { |
303 |
| - // If the closures are strong-referenced, then they might still be |
304 |
| - // referenced from the C side, and those closures might access this |
| 301 | + if box.done || strong { |
| 302 | + // If box.done: |
| 303 | + // The finalizer is again called before goFinishRemovingToggleRef gets |
| 304 | + // the chance to run. Set the finalizer again and move on. |
| 305 | + // |
| 306 | + // If strong: the closures are strong-referenced, then they might still |
| 307 | + // be referenced from the C side, and those closures might access this |
305 | 308 | // object. Don't free.
|
306 | 309 |
|
307 |
| - // Delegate finalizing to the next cycle. |
308 |
| - runtime.SetFinalizer(dummy, finalizeBox) |
309 |
| - |
310 |
| - shared.mu.Unlock() |
311 |
| - |
312 | 310 | if toggleRefs != nil {
|
313 |
| - toggleRefs.Println(objInfoRes, "finalizeBox: moving finalize to next GC cycle") |
| 311 | + if box.done { |
| 312 | + toggleRefs.Println( |
| 313 | + objInfo(dummy.gobject), |
| 314 | + "finalizeBox: finalizeBox called on already finalized object") |
| 315 | + } else { |
| 316 | + toggleRefs.Println( |
| 317 | + objInfo(dummy.gobject), |
| 318 | + "finalizeBox: moving finalize to next GC cycle since object is still strong") |
| 319 | + } |
314 | 320 | }
|
315 |
| - } else { |
316 |
| - // If the closures are weak-referenced, then the object reference hasn't |
317 |
| - // been toggled yet. Since the object is going away and we're still |
318 |
| - // weakly referenced, we can wipe the closures away. |
319 |
| - delete(shared.weak, dummy.gobject) |
320 | 321 |
|
321 |
| - shared.mu.Unlock() |
| 322 | + return |
| 323 | + } |
322 | 324 |
|
323 |
| - // Unreference the object. This will potentially free the object as |
324 |
| - // well. The closures are definitely gone at this point. |
325 |
| - // C.g_object_remove_toggle_ref( |
326 |
| - // (*C.GObject)(unsafe.Pointer(dummy.gobject)), |
327 |
| - // (*[0]byte)(C.goToggleNotify), nil, |
328 |
| - // ) |
329 |
| - |
330 |
| - // Do this in the main loop instead. This is because finalizers are |
331 |
| - // called in a finalizer thread, and our remove_toggle_ref might be |
332 |
| - // destroying other main loop objects. |
333 |
| - C.g_main_context_invoke( |
334 |
| - nil, // nil means the default main context |
335 |
| - (*[0]byte)(C.gotk4_intern_remove_toggle_ref), |
336 |
| - C.gpointer(dummy.gobject)) |
| 325 | + // Mark the box as being removed "done" so that we don't double-free it. |
| 326 | + box.done = true |
337 | 327 |
|
338 |
| - if toggleRefs != nil { |
339 |
| - toggleRefs.Println(objInfoRes, |
340 |
| - "finalizeBox: remove_toggle_ref queued for next main loop iteration") |
341 |
| - } |
| 328 | + // Do this in the main loop instead. This is because finalizers are |
| 329 | + // called in a finalizer thread, and our remove_toggle_ref might be |
| 330 | + // destroying other main loop objects. |
| 331 | + C.g_main_context_invoke( |
| 332 | + nil, // nil means the default main context |
| 333 | + (*[0]byte)(C.gotk4_intern_remove_toggle_ref), |
| 334 | + C.gpointer(dummy.gobject)) |
342 | 335 |
|
343 |
| - if objectProfile != nil { |
344 |
| - objectProfile.Remove(dummy.gobject) |
345 |
| - } |
| 336 | + if toggleRefs != nil { |
| 337 | + toggleRefs.Println( |
| 338 | + objInfo(dummy.gobject), |
| 339 | + "finalizeBox: remove_toggle_ref queued for next main loop iteration") |
346 | 340 | }
|
347 | 341 | }
|
348 | 342 |
|
|
0 commit comments