Collecting Non-Memory Resources

A Problem

Let us consider a small problem. We would like to manage resources using a Haskell program, that are not just memory. For the sake of argument we will consider GPU resources. This can be reasonably straight forwardly done by using the IO monad to essentially write an imperative program that manages the resources. But doesn’t this defeat the point of functional programming? We’re losing so many benefits that we normally get, we no longer get to describe only the result of our program, instead we have to describe how to get to it too. Not only that, but we’ve lost our wonderful garbage collection system that allows us to easily avoid all of those nasty segfaults we see in non-managed languages. So, the problem today is, how do we extend the Haskell garbage collector (preferably without playing with the runtime or compiler) to be able to manage all these resources.

An attempt

Let’s consider only one small subset of GPU resources – a shader. What we would like in our Haskell program is a pure value that represents the shader, which we can call on at a later date. We’d like a function that takes our shader code, and produces this pure value, and we’d like the resources on the GPU to be collected when the value is no longer in scope.

compile :: String -> String -> Shader
compile vertexShdrSrc fragShdrSrc = s
  where
    s = doCompile s vertexShdrSrc fragShdrSrc

{-# NOINLINE doCompile #-}
doCompile :: Shader -> String -> String -> Shader
doCompile  s vertexShdrSrc fragShdrSrc =
  unsafePerformIO $ do
    {- Set up our fancy pants shader stuff here -}
    addFinalizer s {- Remove resources from the GPU here -}

What we hope will happen is that we return our shader – s, with a finalizer attached to it. When the garbage collector collects s, it will also collect the resources off the GPU. This all looks rather good, so lets try using it:

myShader :: Shader
myShader =
  compile "some vertex shader source"
          "some fragment shader source"

The result of evaluating myShader is a constant use of s, the definition of this constant is looked up, and replaces it, so myShader is now defined as the right hand side of s. Unfortunately, there’s now nothing that points at s itself, so it’s garbage collected, and all our resources removed from the graphics card.

Conclusion

We’ve tried to find a way of getting automated collection of non-memory resources, but ultimately, not quite got there. I don’t see a way forward from this point, and would love to hear other people’s input on how this sort of management can be done

3 comments on “Collecting Non-Memory Resources

  1. Simon Marlow says:

    addFinalizer is not the right thing to use in this case. In fact, it’s almost never the right thing to use.

    Finalizers for external resources need to be attached to atomic objects: things like MutVar#, and MVar#. We provide respectively addFinalizerIORef and addFinalizerMVar for these. On IRC recently we discussed having a library to encapsulate this functionality nicely, it would have a signature like:

    data GCToken
    newGCToken :: IO GCToken
    addFinalizerGCToken :: IO () -> GCToken -> IO ()
    touchGCToken :: GCToken -> IO ()

    the idea being that the finalizer(s) would run at some point after the last touchGCToken for that token.

    You can implement the above quite easily using GCToken == IORef, addFinalizerIORef, and defining touchGCToken in terms of the touch# primitive.

  2. How does one use touchGCToken? What does touch# do?

Leave a comment