Hacker News new | ask | show | jobs
by ajross 2612 days ago
Not a wayland, wlroots or rust expert, but dollars to donuts says that it's not "memory" management[1] at issue, but resource allocation on the other side of the graphics driver. Vertex arrays, textures, framebuffers, shaders et. al. all need some kind of allocation strategy, can in the modern world often be shared between process contexts, and they really don't fit well into the metaphors of either C or Rust.

But where in C you can just do it anyway, Rust is likely to flip out over your wrapper object abstractions.

[1] I mean sure, technically it is memory management, but you know what I mean.

4 comments

I have a very simple GPU abstraction in Pathfinder that has to manage lots of these objects, and it was easy and natural to get them to work. Sometimes I have to throw in a reference count here and there to make sure things are destroyed in the right order.

If the model doesn't fit perfectly into Rust's ownership system, I usually just do the checks dynamically and panic, or accept logic bugs (but not memory safety bugs), if things go wrong. Is that admitting failure? Perhaps. But it's also just choosing my battles. I'm trying to keep my code clean and ship, not prove it correct. Memory safety problems are pernicious enough that it's worth significant effort to rule them out, but almost everything else is a judgment call.

This is also something that causes some pain when using C++ with RAII. Logically it makes some amount of sense that you have e.g. a C++ wrapper for a texture in OpenGL, and it knows to call glDeleteTextures in its destructor.

HOWEVER, the destructor can now only be called if the OpenGL context is active, and the texture may be deleted if the context is lost (which can happen). At some point anybody who's tried to wrap an OpenGL texture with a C++ class either decides to accept that RAII doesn't completely work here, or refactors it so that you manage something like handles to textures, which is a bit silly because you are really at that point managing handles to handles to textures.

re: the handles to handles stuff ... I don't think that's so silly really, when you are talking about a resource that in some fundamental sense belongs to a different system (in this case OpenGL). Sure, that system gave you a handle, but if the semantics of managing that handle aren't 1:1 matched with your languages model of things, another level of indirection is a clean way to handle it.
And just think about what most file and I/O wrappers, that are part of the standard library in most higher level languages, e.g. file objects in python, streams in C++ etc., naturally do: They also just wrap (for example) POSIX file descriptors, which are handles, with their own handles, i.e. the file object/Stream/whatever.
Maybe I didn't explain what's happening in OpenGL properly, because that comparison doesn't work at all.

With e.g. std::ofstream, you are wrapping a handle, such as a POSIX file descriptor. So that part of your description is accurate.

However, this is different from what happens with OpenGL. The problem is that if you wrap an OpenGL texture (which is a handle), you end up with some problems because you can only free it with the correct context active, and it might become free for other reasons besides the wrapper being destroyed. So you are no longer wrapping a handle (like std::ofstream), but you are actually creating a new handle-to-handle, and wrapping that, and explicitly managing the lifetime OpenGL resources with a separate object somewhere else.

Of course, you could just wrap an OpenGL texture, explicitly decide not to handle context invalidation, and then be careful to ensure that your objects get destroyed with the OpenGL context active. You lose some flexibility and you have to babysit RAII to be sure it "does the right thing".

So what I'm saying here is that OpenGL textures are not like file handles.

They don't behave like file handles - but at a certain distance if you squint at it looks like the same problem. The real issue isn't that OpenGL textures don't behave like file handles, but that neither of them behave quite like objects in your language, which can lead to problems. The specifics of how the behavior differs is I think less important that the fact that it differs. Of course, file handles are not the best example because the language designers often had to put some thought into it early on; less true of something like OpenGL textures.
> …but at a certain distance if you squint at it looks like the same problem…

The whole discussion here is about how these things differ in subtle ways. The problem with “squinting” at the problem is you end up e.g. using RAII and then having to redesign your system because RAII doesn’t match, and the problems weren’t obvious when you started out.

Not to make too fine a point about it here, but file handles are perfect matches for RAII / Rust lifetimes, unlike OpenGL textures. You just close the handle at end of object lifetime. You can get errors when you close but current recommendation is to ignore errors when cleaning up and have a separate path for closing/committing data on close when writing, so on most paths the destructor will nop. This works well.

An example of a mismatch with the language semantics and file handles is with GC. If you close a file handle in a C++ destructor or Rust drop(), it’s fine, those are run deterministically. If you use a JVM finalizer to close your file handle that might not happen soon enough (cue EMFILE).

Again, the reason why textures don’t work this way is because you have to release them in a certain context and they might be released outside your control.

> ... you end up with some problems because you can only free it with the correct context active, ...

I wonder if this is where Rust can actually help. I.e. could the lifetime tracking in the language allow you to construct a library where the unref was guaranteed to happen only when the right context is active?

And if such a library could be constructed, what would the loss of flexibility cost?

Incidentally, some of the people working on safe OpenGL wrappers ran into similar issues with the blog post, thanks to similar problems.
Right, it's the same basic problem, and a good pattern to solve it.
The problematic pattern I see here is whenever you manage remote state, your local guarantees about how that state changes just don't work.

You would love to have the abstraction of a standalone texture and be able to pass it around as a value and change it's ownership, but you never really owned it. The only thing you ever owned is the context (or the connection in the case of the wayland protocol). The texture you borrowed. Freeing your texture is equivalent to returning it to the context you borrowed it from. Of course not having an explicit context in the OpenGL API makes this much harder :)

Yes, a similar issue exists in C++ too when managing some types of resources in database kernels. Some types of resource management are a poor fit for idiomatic ("safe") resource management in C++. In that language, you always have the option of custom, non-idiomatic resource management -- everything you might want to do is always possible in principle. You avoid it to the extent practical but there are a few times when this kind of resource management is unambiguously the correct software design and engineering choice. It is a bit like "goto". Rarely used for good reason but there are times when any other implementation will be decidedly much worse.

I've hashed this out extensively when looking at implementing a database kernel in Rust (I’ve had Rust experts among my C++ programmers). Technically it is possible to implement these things in a safe way in both C++ and Rust. However, it comes at a cost of an unreasonably complex and inefficient software architecture in either language, so you would not want to actually do it that way in a sane code base.

Wayland uses "objects" that can appear and disappear and graphic driver is unrelated here. For example, wayland has a "registry" and it sends notifications to clients when new objects appear there or disappear from it. I assume this unpredictability could become the problem for the author.