/lib/pycall/gc_guard.rb
https://github.com/mrkn/pycall.rb · Ruby · 84 lines · 66 code · 14 blank · 4 comment · 3 complexity · 8488e4a4327c90f2fb1a49f01f9a2fa4 MD5 · raw file
- require 'pycall'
- module PyCall
- module GCGuard
- @gc_guard = {}
- class Key < Struct.new(:pyptr)
- def initialize(pyptr)
- self.pyptr = check_pyptr(pyptr)
- # LibPython.Py_IncRef(pyptr)
- end
- def release
- # LibPython.Py_DecRef(pyptr)
- self.pyptr = nil
- end
- def ==(other)
- case other
- when Key
- pyptr.pointer == other.pyptr.pointer
- else
- super
- end
- end
- alias :eql? :==
- def hash
- pyptr.pointer.address
- end
- private
- def check_pyptr(pyptr)
- pyptr = pyptr.__pyobj__ if pyptr.respond_to? :__pyobj__
- return pyptr if pyptr.kind_of? LibPython::PyObjectStruct
- raise TypeError, "The argument must be a Python object"
- end
- end
- def self.register(pyobj, obj)
- key = Key.new(pyobj)
- @gc_guard[key] ||= []
- @gc_guard[key] << obj
- end
- def self.unregister(pyobj)
- key = Key.new(pyobj)
- @gc_guard.delete(key).tap { key.release }
- end
- def self.guarded_object_count
- @gc_guard.length
- end
- def self.embed(pyobj, obj)
- pyptr = pyobj.respond_to?(:__pyobj__) ? pyobj.__pyobj__ : pyobj
- raise TypeError, "The argument must be a Python object" unless pyptr.kind_of? LibPython::PyObjectStruct
- wo = LibPython.PyWeakref_NewRef(pyptr, weakref_callback)
- register(wo, obj)
- pyobj
- end
- private_class_method def self.weakref_callback
- unless @weakref_callback
- @weakref_callback_func = FFI::Function.new(
- LibPython::PyObjectStruct.ptr,
- [LibPython::PyObjectStruct.ptr, LibPython::PyObjectStruct.ptr]
- ) do |callback, pyptr|
- GCGuard.unregister(pyptr)
- # LibPython.Py_DecRef(pyptr)
- # LibPython.Py_IncRef(PyCall.None)
- next PyCall.None
- end
- method_def = LibPython::PyMethodDef.new("weakref_callback", @weakref_callback_func, LibPython::METH_O, nil)
- @weakref_callback = LibPython.PyCFunction_NewEx(method_def, nil, nil).tap {|po| LibPython.Py_IncRef(po) }
- end
- @weakref_callback
- end
- end
- private_constant :GCGuard
- end