From 11dd7f12a5cd8cbc604f2a1693d0cf5801f3c5f4 Mon Sep 17 00:00:00 2001 From: Vadim Kononov Date: Mon, 28 Aug 2023 12:31:10 -0500 Subject: [PATCH] Fix defining object finalizer in Ruby 3.1 to reference a UUID string In Ruby 3.1, accessing callback_queue from a finalizer causes an "undefined local variable or method `callback_queue' for FiniteMachine::Observer:Class (NameError)", and callback_queue is never cleaned up. However, making the finalizer an instance method rather than a class method throws a warning about the improper use of finalizers. Hence, using the UUID string as an object to check before it is destroyed by garbage collection for when to call the finalizer proc. --------- Co-authored-by: Piotr Murach --- CHANGELOG.md | 4 +++ lib/finite_machine/observer.rb | 50 +++++++++++++++++++++------------- 2 files changed, 35 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b181f60..b251dbf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Change Log +### Fixed +* Fix defining object finalizer in Ruby 3.1 to reference a UUID string + by Vadim Kononov(@vkononov) + ## [v0.14.0] - 2020-09-12 ### Added diff --git a/lib/finite_machine/observer.rb b/lib/finite_machine/observer.rb index 4591d57..ec31b2a 100644 --- a/lib/finite_machine/observer.rb +++ b/lib/finite_machine/observer.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +require "securerandom" + require_relative "async_call" require_relative "callable" require_relative "hook_event" @@ -13,20 +15,6 @@ module FiniteMachine class Observer < GenericDSL include Safety - # Clean up callback queue - # - # @api private - def self.cleanup_callback_queue - proc do - begin - if callback_queue.alive? - callback_queue.shutdown - end - rescue MessageQueueDeadError - end - end - end - # The current state machine attr_reader :machine @@ -44,11 +32,6 @@ def initialize(machine) @hooks = Hooks.new @machine.subscribe(self) - ObjectSpace.define_finalizer(self, self.class.cleanup_callback_queue) - end - - def callback_queue - @callback_queue ||= MessageQueue.new end # Evaluate in current context @@ -204,6 +187,35 @@ def defer(callable, trans_event, *data) callback_queue << async_call end + # Get an existing callback queue or create a new one + # + # @return [FiniteMachine::MessageQueue] + # + # @api private + def callback_queue + @callback_queue ||= MessageQueue.new.tap do + @queue_id = SecureRandom.uuid + ObjectSpace.define_finalizer(@queue_id, proc do + cleanup_callback_queue + end) + end + end + + # Clean up the callback queue + # + # @return [Boolean, nil] + # + # @api private + def cleanup_callback_queue + ObjectSpace.undefine_finalizer(@queue_id) if @queue_id + return unless @callback_queue && callback_queue.alive? + + begin + callback_queue.shutdown + rescue MessageQueueDeadError + end + end + # Create callable instance # # @api private