/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

  1. require 'pycall'
  2. module PyCall
  3. module GCGuard
  4. @gc_guard = {}
  5. class Key < Struct.new(:pyptr)
  6. def initialize(pyptr)
  7. self.pyptr = check_pyptr(pyptr)
  8. # LibPython.Py_IncRef(pyptr)
  9. end
  10. def release
  11. # LibPython.Py_DecRef(pyptr)
  12. self.pyptr = nil
  13. end
  14. def ==(other)
  15. case other
  16. when Key
  17. pyptr.pointer == other.pyptr.pointer
  18. else
  19. super
  20. end
  21. end
  22. alias :eql? :==
  23. def hash
  24. pyptr.pointer.address
  25. end
  26. private
  27. def check_pyptr(pyptr)
  28. pyptr = pyptr.__pyobj__ if pyptr.respond_to? :__pyobj__
  29. return pyptr if pyptr.kind_of? LibPython::PyObjectStruct
  30. raise TypeError, "The argument must be a Python object"
  31. end
  32. end
  33. def self.register(pyobj, obj)
  34. key = Key.new(pyobj)
  35. @gc_guard[key] ||= []
  36. @gc_guard[key] << obj
  37. end
  38. def self.unregister(pyobj)
  39. key = Key.new(pyobj)
  40. @gc_guard.delete(key).tap { key.release }
  41. end
  42. def self.guarded_object_count
  43. @gc_guard.length
  44. end
  45. def self.embed(pyobj, obj)
  46. pyptr = pyobj.respond_to?(:__pyobj__) ? pyobj.__pyobj__ : pyobj
  47. raise TypeError, "The argument must be a Python object" unless pyptr.kind_of? LibPython::PyObjectStruct
  48. wo = LibPython.PyWeakref_NewRef(pyptr, weakref_callback)
  49. register(wo, obj)
  50. pyobj
  51. end
  52. private_class_method def self.weakref_callback
  53. unless @weakref_callback
  54. @weakref_callback_func = FFI::Function.new(
  55. LibPython::PyObjectStruct.ptr,
  56. [LibPython::PyObjectStruct.ptr, LibPython::PyObjectStruct.ptr]
  57. ) do |callback, pyptr|
  58. GCGuard.unregister(pyptr)
  59. # LibPython.Py_DecRef(pyptr)
  60. # LibPython.Py_IncRef(PyCall.None)
  61. next PyCall.None
  62. end
  63. method_def = LibPython::PyMethodDef.new("weakref_callback", @weakref_callback_func, LibPython::METH_O, nil)
  64. @weakref_callback = LibPython.PyCFunction_NewEx(method_def, nil, nil).tap {|po| LibPython.Py_IncRef(po) }
  65. end
  66. @weakref_callback
  67. end
  68. end
  69. private_constant :GCGuard
  70. end