Project

General

Profile

Actions

Feature #16471

open

Two feature requests for WeakRef: get original object, callback feature

Added by Snappingturtle (Mike O'Sullivan) about 4 years ago. Updated about 4 years ago.

Status:
Open
Assignee:
-
Target version:
-
[ruby-core:96618]

Description

I'd like to request two features for WeakRef. I'll explain what I want, then provide a real world use case.

First, add the ability to pull the original object out of the WeakRef object, something like this:

myhash = {}
myhash = WeakRef.new(myhash)
myhash = myhash.original_object()

Second, add a callback feature for when a WeakRef's object is being purged by GC. It would work something like this:

wr = WeakRef.new(myobject)

wr.on_garbage() do |wr|
    puts 'trashing'
end

# time goes by...
# myobject goes out of scope
# outputs "trashing"

Here's the specific use case I would want it for. I'm developing a database system which includes a class called Node. A Node object holds a reference to a database handle and the primary key of a record in that database. It also has methods for getting and setting values in that record. So a simplified version looks something like this:

class Node
    attr_reader :dbh
    attr_reader :pk
    
    def initialize(dbh, pk)
        @dbh = dbh
        @pk = pk
    end
    
    def set(fieldname, value)
        # a bunch of SQL to set the value
    end
    
    def get(fieldname)
        # a bunch of SQL to get the value
    end
end

node = Node.new dbh, 'abc'
node.set 'name', 'Fred'

The database object will have a node method, so you would usually get nodes like this:

node = dbh.node('abc')

Make sense so far? It's a pretty simple concept. It works, but I'd like to make a small improvement. (Whether or not it's actually an improvement is a judgement call... I expect some disagreement on this point. But work with me here.)

I'd like the database object to keep a cache of Node objects. However, the database doesn't keep those node objects alive: they can fall out of scope and get purged by the GC. However, if that cache is never purged of dead objects, it just grows bigger and bigger. I could occasionally just run a routine to work through the cache and purge dead references, but that seems very inefficient. It would be better to just have them purged as they die.

So I could implement it something like this:

class DataBase
    attr_reader :cache
    
    def initialize
        @cache = {}
    end
    
    def node(pk)
        if @cache[pk]
            # Here's where we need to get at the original object
            return @cache[pk].original_object
        else
            new_node = Node.new(self, pk)
            
            @cache[pk] = WeakRef.new(new_node)
            
            # Here's where we set the callback
            @cache[pk].on_garbage do |wr|
                wr.db.cache.delete wr.pk
            end
            
            return new_node
        end
    end
end

So when a Node object is garbage collected, it's deleted from the cache. The cache stays clean of dead objects.

I'll be interested to hear what you think of this idea and how difficult it would be to implement it.


Related issues 1 (0 open1 closed)

Related to Ruby master - Feature #16038: Provide a public WeakMap that compares by equality rather than by identity ClosedActions
Actions #1

Updated by nobu (Nobuyoshi Nakada) about 4 years ago

  • Related to Feature #16038: Provide a public WeakMap that compares by equality rather than by identity added

Updated by nobu (Nobuyoshi Nakada) about 4 years ago

  • Tracker changed from Bug to Feature
  • Description updated (diff)
  • ruby -v deleted (ruby 2.5.1p57 (2018-03-29 revision 63029) [x86_64-linux-gnu])
  • Backport deleted (2.5: UNKNOWN, 2.6: UNKNOWN)

First, add the ability to pull the original object out of the WeakRef object,

WeakRef#__getobj__.

Second, add a callback feature for when a WeakRef's object is being purged by GC.

            # Here's where we set the callback
            @cache[pk].on_garbage do |wr|
                wr.db.cache.delete wr.pk
            end

This usage would be impossible even if the callback were provided.
The callback will be called after wr was collected.
Probably you may be interested in [Feature #16038].

Updated by Snappingturtle (Mike O'Sullivan) about 4 years ago

WeakRef#__getobj__ isn't documented in Ruby 2.5.1 (https://short.uno/wwb6bky) so I didn't know it existed. So I guess that's sort of a bug report right there.

I think I might be able to write a class that does the callback on garbage collection. If so, I'll post a gem.

Updated by byroot (Jean Boussier) about 4 years ago

WeakRef#__getobj__ isn't documented in Ruby 2.5.1

It kinda is, but I agree it's not very clear. The documentation says the parent class is Delegator and __getobj__ is documented there: https://ruby-doc.org/stdlib-2.5.3/libdoc/delegate/rdoc/Delegator.html

Second, add a callback feature for when a WeakRef's object is being purged by GC.

Can't you just use ObjectSpace.define_finalizer ? https://ruby-doc.org/core-2.6.3/ObjectSpace.html#method-c-define_finalizer

Updated by Snappingturtle (Mike O'Sullivan) about 4 years ago

I'd rather not pollute the target object's class with a finalizer. However, I'm working on a class, FinalCall, that can hold the target object. When the FinalCall object is garbage collected, it calls a callback. If anybody wants to contact me outside this thread, I could use some help on it.

Actions

Also available in: Atom PDF

Like0
Like0Like0Like0Like0Like0