Skip to content

Dropping an RC'd reference within it's own signal causes a hard crash. #1666

Description

@greenfox1505

I have an RefCounted called "watcher". It emits a signal. When this signal is called, the watcher is no longer needed, so I null the Watcher object. This causes a full game crash in Rust.

ERROR: Destroyed an object from Godot side, while a bind() or bind_mut() call was active.
  This is a bug in your code that may cause UB and logic errors. Make sure that objects are not
  destroyed while you still hold a Rust reference to them, or use Gd::free() which is safe.
  object: Base { id: -9223372000381106669, class: DropWatcher, refc: 0 }
   at: crash (core/core_bind.cpp:349)
   GDScript backtrace (most recent call first):
       [0] test_nullifier (res://rust_drop_test.tscn::GDScript_u86e1:66)
       [1] _ready (res://rust_drop_test.tscn::GDScript_u86e1:51)

GDScript doesn't do this. An object can null itself causing a destroy when the signal is finished. I think in Godot C++, there might be something holding a self reference and that doesn't get release until the call stack is finished? (speculating, haven't read C++ source on it)

A work around with call_deferred does work:

class Watcher extends RefCounted:
	signal test_signal
	func emit_test_signal():
		test_signal.emit()

var rust_watcher = DropWatcher.make_with_label("isolated watcher")
var gd_watcher = Watcher.new()
func _ready() -> void:
	print("==== test: GDScript Watcher Nullify")
	gd_watcher.test_signal.connect(func():
		gd_watcher = null
		)
	gd_watcher.emit_test_signal()
	print("should be null:",gd_watcher)

	print("==== test: Rust Watcher Nullify")
	rust_watcher.test_signal.connect(func(data):
		print("dropping ref")
		call_deferred("set","rust_watcher",null)
		#rust_watcher = null # <-- commentating this line causes a full game crash
		)
	print("emitting")
	rust_watcher.emit_test_signal()
	print("should be null:",rust_watcher)
	await get_tree().process_frame
	print("should be null:",rust_watcher)
	pass

DropWatcher coppied from previous bug, #1665:

#[derive(GodotClass)]
#[class(base=RefCounted,no_init)]

struct DropWatcher {
    label: GString,
    base: Base<RefCounted>,
}

#[godot_api]
impl IRefCounted for DropWatcher {}

#[godot_api]
impl DropWatcher {
    #[func]
    fn make_with_label(label: GString) -> Gd<Self> {
        Gd::from_init_fn(|base| Self { label, base })
    }
    #[func]
    fn emit_test_signal(&mut self) {
        let l = self.label.clone();
        self.signals().test_signal().emit(&l);
    }

    #[signal]
    fn test_signal(label: GString);
}

impl Drop for DropWatcher {
    fn drop(&mut self) {
        godot_print!("{} dropped!", self.label);
    }
}

Drop is not strictly necessary, only used here to detect drop event.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugc: ffiLow-level components and interaction with GDExtension API

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions