3

To learn C# native interop, I've been working on an OpenGL wrapper. The OpenGL API itself is a state machine which is bound to a specific thread. When an object containing native resources is garbage collected, the finalizer is running in the GC thread, and cannot directly free the resources.

The workaround I currently have is to have a list in the context object, which the objects add their resources to and at a safe point in the draw loop it iterates through and frees them.

The problem with this, however, is that if the GC collects while it's iterating through that list, the foreach fails as the collection has been modified. I can't just put a mutex around the list as the GC is stop-the-world in most implementations and if the draw loop had locked it, it'd never complete the iteration and unlock it again.

Typically the MTBF is about two hours of gameplay, but if intentionally stress tested with a few thousand objects per second it happens in just a few seconds.

What might be the best approach here?

jameswilddev
  • 582
  • 7
  • 16
  • Are you using the dispose pattern in the container to free up the resources in the thread before termination of the thread? – Preet Sangha May 31 '13 at 10:17
  • Have you tried [disabling concurrent GC](http://msdn.microsoft.com/en-us/library/at1stbec.aspx)? Not really sure if it relates, but might be worth testing out. – Adam Houldsworth May 31 '13 at 10:17
  • 1
    I can't help thinking that the best option here would be to use `using` / `Dispose()` rather than a finalizer to release these resources – Marc Gravell May 31 '13 at 10:19
  • The dispose pattern is not in use here; the objects are created through an asset manager to prevent the same models/textures being loaded into VRAM hundreds of times. If a car loaded a model and then disposed of it when it was removed from the scene all of the cars would lose their models. I doubt disabling concurrent GC would help much; could it still collect in the foreach? The using/Dispose() pattern is generally useful but not in this context; entities generate these objects, use them when Draw() is called, and hang onto them until they are destroyed. Thanks for your input so far. – jameswilddev May 31 '13 at 10:26
  • 1
    Could you use some sort of reference counting for the model data? So only free the unmanaged data if during a call to `Dispose` the reference count gets to 0? – Dirk May 31 '13 at 10:43
  • Had thought about doing that, but reference counting really isn't ideal and I'd like to avoid it if possible. I guess I could create a "ticket" object whenever an asset is asked for, and use it to prevent spurious reference freeing (if the ticket isn't in the list, throw an exception, otherwise remove it, if the list is empty, free the resource). – jameswilddev May 31 '13 at 10:50

4 Answers4

1

Then you're going to bite the bullet and stop relying on the GC to do your resource management for you. You're going to have to have your asset manager have an explicit function to delete the objects it allocates, rather than relying on the asset manager's finalizer function. And you're going to have to call that function at a specific place in your code.

Just because you have GC doesn't mean it's the best or only solution.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
1

Questioner wrote/stated:

The OpenGL API itself is a state machine which is bound to a specific thread.

This is wrong!

OpenGL contexts can be active in only one thread at a time. This doesn't mean, contexts can not be used from different threads. Essentially a OpenGL context is a mutually exclusive resource (hint: Mutex) that is to be bound before using it ({wgl,glX}MakeCurrent(DC, RC)) in a thread and after being done with whatever the context was required for, you unbind from the current thread the OpenGL context ({wgl,glX}MakeCurrent(NULL, NULL)).

datenwolf
  • 159,371
  • 13
  • 185
  • 298
  • I can't just take over control of the context from the GC thread (which may have stopped the world) at a completely random time though right? Currently using GLFW to open the window so I don't have access to the arguments I would need to supply, though that's likely to go eventually. Thanks for the clarification. – jameswilddev May 31 '13 at 13:59
0

You could always make use of the fixed statement.

See here http://msdn.microsoft.com/en-us/library/f58wzh21%28VS.80%29.aspx

jaywayco
  • 5,846
  • 6
  • 25
  • 40
  • not sure how that applies to the question. The question doesn't seem to be talking about unmanaged pointers. Just thread-bound resources. – Marc Gravell May 31 '13 at 10:20
  • The question is talking about preventing the GC from collecting in a specific scope of execution. That is exactly what the fixed statement is for. You are correct though the fixed statement does require use of unmanaged code, but stopping the GC from collecting is essentially that. – jaywayco May 31 '13 at 10:28
  • please read the link completely, you will see an example of how you can use the fixed statement to pin a managed variable – jaywayco May 31 '13 at 10:34
  • @jaywayco again, this question **is not about pinning**. It isn't about pointers changing their value due to compaction - it is related to threads – Marc Gravell May 31 '13 at 10:37
  • 1
    Thanks again for your input, but the issue at heart is that I need a way to pass references from the GC thread to a specific thread, which is difficult as the GC is stop-the-world on some platforms meaning using synchronization objects will just hang the program as the iteration through the list of resources to dispose will never complete. – jameswilddev May 31 '13 at 10:40
  • Thanks for the explanation @jameswilddev. Things tend to stay much friendlier when people issue constructive criticism of an answer. I apologise for getting my wires crossed – jaywayco May 31 '13 at 10:45
0

It sounds as though your real problem is that you have objects with finalizers that expose their resources. If you are going to be using resources directly, you should create and store them in objects without finalizers, and make certain you exercise the discipline necessary to prevent resource leaks.

supercat
  • 77,689
  • 9
  • 166
  • 211