<span class="Apple-style-span" style="font-family:arial,sans-serif;font-size:13px;background-color:rgb(255,255,255)">I've been working on a c++ wrapper around rabbitmq-c that presents a "humane" API to the programmer heavily inspired by the Puka project. (see: </span><a href="https://github.com/alanxz/SimpleAmqpClient" target="_blank" style="color:rgb(0,0,204);font-family:arial,sans-serif;font-size:13px;background-color:rgb(255,255,255)">https://github.com/alanxz/SimpleAmqpClient</a><span class="Apple-style-span" style="font-family:arial,sans-serif;font-size:13px;background-color:rgb(255,255,255)">).</span><div style="font-family:arial,sans-serif;font-size:13px;background-color:rgb(255,255,255)">
<br></div><div style="font-family:arial,sans-serif;font-size:13px;background-color:rgb(255,255,255)">In developing this library I've run across several limitations of the rabbitmq-c when working with multiple channels, the biggest issue being:</div>
<div style="font-family:arial,sans-serif;font-size:13px;background-color:rgb(255,255,255)"><br></div><div style="font-family:arial,sans-serif;font-size:13px;background-color:rgb(255,255,255)">- There is no way to wait for a list of methods on a channel. </div>
<div style="font-family:arial,sans-serif;font-size:13px;background-color:rgb(255,255,255)">There is amqp_simple_wait_method() - however this suffers from some serious drawbacks: </div><div style="font-family:arial,sans-serif;font-size:13px;background-color:rgb(255,255,255)">
+ you can only specify one method to listen for</div><div style="font-family:arial,sans-serif;font-size:13px;background-color:rgb(255,255,255)">+ it calls abort() if a different method, or a method on a different channel is received</div>
<div style="font-family:arial,sans-serif;font-size:13px;background-color:rgb(255,255,255)">A use case for this might be: doing a basic.publish, and you want to want to wait for a basic.ack or a basic.return on a channel with confirms enabled</div>
<div style="font-family:arial,sans-serif;font-size:13px;background-color:rgb(255,255,255)"><div><br></div><div>The way I got around this in SimpleAmqpClient was to only use amqp_simple_wait_frame() and maintain queues of amqp_frame_t for each channel that I have open. </div>
<div><br></div><div>However, this comes with one serious drawback: memory management. Each decoded frame is allocated in a connection-wide amqp_pool_t. Because of this - it is impossible to recycle the pool and release memory unless you have dealt with all of your pending amqp_frame_t's. This becomes a problem in pathological cases where you have two consumers sending simultaneously, you can get in the situation that even though the client program eventually deals with every consumed message, memory never gets released, because there is always at least one frame queued up.</div>
<div><br></div><div>The above demonstrates the second biggest issue with the rabbitmq-c API: memory management when dealing with multiple channels. There is no way to separate out memory allocation on, for example, a per-channel basis (with the library client keeping track of the memory pools used for example).</div>
<div><br></div><div>Before I go on I'd like to mention one feature that I find useful with the current API: it is possible to use something like select() before calling amqp_simple_wait_frame() to setup a timeout while waiting for a consumer, which is useful when developing single-threaded RPC apps. </div>
<div><br></div><div><br></div><div>So now the interesting part: how could the API be improved? </div><div><br></div><div>Some thoughts I've had dealing with the memory management:</div><div>1. Create a amqp_simple_wait_frame() variant that as one of the parameters provides a callback to allocate memory, either something like a malloc() call, which the client would then be responsible for freeing, or perhaps something like get_amqp_pool(amqp_frame_t) which allows the client to return a memory pool which rabbitmq-c would use to allocate memory for that frame. The amqp_frame_t would have to have some minimal amount of information filled in - such as frame type and channel to be useful.</div>
<div><br></div><div>2. amqp_channel_t becomes a struct containing both the the channel_id and a amqp_pool_t. The amqp_pool would be used by the library to allocate frames received on that channel. The client would then be responsible for calling amqp_pool_recycle at an appropriate point.</div>
<div><br></div><div>Some thoughts on improving the API to deal with multiple channels:</div><div>1. Add the following to the API:</div><div>amqp_simple_wait_frame_on_channel - wait for a frame on a specified channel</div>
<div>amqp_simple_wait_methods - wait for multiple methods on a specified channel. Don't abort() if the wrong method, or channel is received, instead queue up the frame as is done in amqp_simple_rpc</div><div>amqp_frames_enqueued_for_channel to add feature parity with amqp_frames_enqueued</div>
<div>I started to code this up at one point but abandoned it as it didn't support the interesting property of being able to use select() to specify a timeout when calling amqp_simple_wait_frame_on_channel. At least not without adding timeout to the rabbitmq-c api, which didn't fit with how the current rabbitmq-c api. Here's the implementation I came up with before I abandoned it. I can't guarantee is completely free of bugs. <a href="https://github.com/alanxz/rabbitmq-c/commits/simple_wait_frame/" target="_blank" style="color:rgb(0,0,204)">https://github.com/alanxz/rabbitmq-c/commits/simple_wait_frame/</a></div>
<div><br></div><div>2. Continue to duck the issue and allow clients to write their own code to deal with multiple channels (fixing the memory management issue using one of the above techniques)</div><div><br></div><div>3. Something I haven't thought of yet.</div>
<div><br></div><div>So anyone have any thoughts on all of this?</div><div><br></div><font color="#888888"><div>-Alan</div></font></div>