Why does quartz deadlock? Read requests shouldn't block, and while sample requests can block they should be resolved by earlier samples getting delivered, which shouldn't deadlock.
Because it synchronizes streams together in a way that's is supposed to be. You don't know whether read requests are blocking or not, as soon as it goes back to application code it can be anything.
Yes, sure, a quartz client can do anything in its COM callbacks, and of course the documentation drastically underspecifies what's allowed, but we can't afford to test and anticipate every possible deadlock, and I think it's a viable approach to anticipate the common and conceptually sensible usage, and then deal with deadlocks later if we do come across them.
Conceptually a read request might not be filled instantaneously, but it shouldn't *block* on anything else related to quartz.
Sample allocators definitely are blocking, depending on the pool sizes and synchronizing stream threads toghether is not a good idea. We should not assume anything about it, and we should instead make sure that the code is flexible and avoids unncessary synchronization on our side.
Here too: the way a quartz allocator is supposed to work is that it sets up a pool of a fixed size, which the peer knows, and it's explicitly supposed to wait in IMemAllocator::GetBuffer() if all of the buffers are currently in use. I can see an application violating the first part, and we effectively can't depend on the number of buffers anyway, but I think the second part again holds conceptually; I don't see any good reason an allocator would block in GetBuffer() on something else related to quartz.
We also have no control over the GStreamer decisions, or its queueing strategy, and whether it will deliver output buffer soon enough to unblock allocators is beyond our reach.
I guess conceptually the problem here is that a GStreamer element might request and fill exactly as many buffers as we have in our quartz pool, but not actually chain any of them to us, and demand to read more before chaining (but not to allocate more samples)? That's quite pathological, does this actually happen?
Or is there a simpler deadlock I'm missing? I can't come up with anything that's not along the lines of "GStreamer demands more buffers than the quartz pool has", which would deadlock *anyway* regardless of whether it's synchronized with read requests.
Sure, but there's also no known application that depends on that detail. I don't like the idea of making the parser interface that much more complicated if it's not necessary.
I'd appreciate something more detailed than "much more complicated" because saying that it is isn't enough to make a case.
I could for instance say the opposite, then point out that:
it reduces the number of necessary entry points, ultimately:
wait_request, push_data, read_data, done_alloc,
vs
get_next_read_offset, push_data, stream_get_buffer, stream_copy_buffer, stream_release_buffer,
All else aside, I'd really rather have many simple and modular entry points than one complex one. In general, writing a lot of boilerplate is preferable to me than writing code that's hard to follow.
- the design doesn't force anything upon the clients, wait_request can be called concurrently, as long as the stream / request masks are disjoint.
I'm less worried about the simplicity of the interface (although I do think there are ways we could make this design friendlier) and more worried about the back-end code. I think it's very hard to follow already, and this is going to make it worse.
- replying to requests is done in a single call, and doesn't require the client to correctly sequence the get_buffer / copy_buffer / release_buffer calls.
Yeah, although I want those to go away anyway. They're pretty much an artifact of either history or just bad design on my part (or both). If we didn't care about zero-copy, it should just be one API that returns ERROR_INSUFFICIENT_BUFFER, or partially fills the buffer, or something.