Better handling of nested memory categories (like adding debug.getmemorycategory)

As a Roblox developer, it is currently too hard to work with nested memory categories.


Problem case:

-- Script
local Module = require(script.Module)

Module.step()
-- Module
local function doExpensiveOperation()
    debug.setmemorycategory("Module.ExpensiveOperation")
    -- do something...
    debug.resetmemorycategory() -- Oops! We reset the category to "Script" instead of "Module"
end

function module.step()
    debug.setmemorycategory("Module")
    doExpensiveOperation()
    -- Oops! Our memory category was reset to "Script" instead of "Module",
    -- so doSomethingElse() will be categorized wrong!
    doSomethingElse()
    debug.resetmemorycategory()
end

Potential solution: debug.withmemorycategory
-- Script
local Module = require(script.Module)

Module.step()
-- Module
local function doExpensiveOperation()
    debug.withmemorycategory("Module.ExpensiveOperation", function()
        -- do something...
    end)
end

function module.step()
    debug.withmemorycategory("Module", function()
        doExpensiveOperation()
        doSomethingElse()
    end)
end
Potential solution: debug.getmemorycategory
-- Script
local Module = require(script.Module)

Module.step()
-- Module
local function doExpensiveOperation()
    local preMemoryCategory = debug.getmemorycategory()
    debug.setmemorycategory("Module.ExpensiveOperation")
    -- do something...
    debug.setmemorycategory(preMemoryCategory)
end

function module.step()
    debug.setmemorycategory("Module")
    doExpensiveOperation()
    doSomethingElse()
    debug.resetmemorycategory()
end

Comments

Most big games use a lot of modules. Assigning memory to the script that started a coroutine is an okay and very flexible default, but with a lot of modules may be misleading. We wouldn’t mind working around this, but we can’t do so comprehensively! In the case that there are nested calls that we want to track the memory of, it’s impossible to return back to the memory category one level up the stack. We either reset back to the default category or we guess what the memory category was (by e.g. looking at the stack with debug.info). This is inaccurate and might lead some developers to spend time investigate the incorrect modules for memory use.

A comprehensive solution to this is a built-in function that switches to a category on the start of a function call and switches back to the pre-call custom category on end or on error*. A simpler solution is a getmemorycategory function which would let developers implement withmemorycategory themselves.

The only possible work-around for this problem case is to wrap every setmemorycategory call to implement getmemorycategory ourselves. This is not compatible between modules by different authors, so using any third party code risks your memory categories being incorrect.

* switching back on error is important because a coroutine can continue after error with pcall and errors are a likely place that custom memory category stacks would get tripped up on.


If this issue is addressed, it would improve my development experience because I will have more accurate memory categories to work with, making debugging memory leaks significantly easier.

13 Likes

Thank you for the suggestion.

I think getmemorycategory might be a function that we’ll be able to provide.
And maybe setmemorycategory can be extended to return the name of the previous memory category to save the extra call.

withmemorycategory can then be built on top of those, like you’ve mentioned.

12 Likes

Bumping this feature request. Either one of these proposed solutions would be incredibly helpful in tracking down memory leaks. Has this been triaged internally?

1 Like

If no implementation issues are found, I expect getmemorycategory(): string and setmemorycategory(string): string to be available at the beginning of April.

5 Likes

New methods are available, documentation will be updated later.

2 Likes