CZMQ appears quite explicit about ownership of objects. For example:
zmsg_send
will destroy the message when sent. This includes destroying any frames that are owned by thezmsg_t
.- Calling
zmsg_add
means that thezmsg_t
takes ownership of thezframe_t
.
I was thinking about making this "ownership" concept an explicit state in the ruby wrappers. So:
zmq_message_wrapper
would have a flag for "owning the message", which would be cleared when we send the message.zmq_frame_wrapper
would have a flag for "owning the message", which would be cleared when the frame belongs to a messagezmq_frame_wrapper
would also have a pointer to thezmq_message_wrapper
who owns it, or NULL.- a Frame would be accessible if ruby owns the frame or if ruby owns the message that owns the frame.
- Ruby's garbage collection of wrapped objects would be simply based on the "owning" state, without needing any callbacks from CZMQ on free/destroy. Only free it if ruby owns it.
- Any ruby object that doesn't "own" its wrapped CZMQ object would not allow access to its underlying data. Instead it could return nil (or raise exception?) For example:
msg = ZMQ::Message.new
frame = ZMQ::Frame.new("hello")
frame.data #=> 'hello'
msg.add(frame)
msg.size #=> 1
frame.data #=> 'hello'
socket.send_message msg
frame.data #=> nil
msg.size #=> nil #
- These "owned" objects could have a
gone?
method that returns true when the underlying CZMQ object has been sent or is no longer accessible to ruby. (would this be required??) - Another example:
request = router_socket.recv_message #=> message could contain 3 parts.
frame = request.unwrap #=> message now contains only last frame
frame.data #=> 'xyz' (the id of the REQ socket)
frame #=> id of sender, ruby owns this frame now.
reply = ZMQ::Message.new
reply.addstr("response")
reply.wrap(frame) #=> reply message contains 3 parts, including the 'frame' one above.
frame.gone? #=> false # the frame is owned by the message which is owned by ruby, so it's still accessible.
frame.data #=> 'xyz' (the id of the REQ socket)
router_socket.send_message(reply) #=> now the 'reply' message is owned by CZMQ
reply.gone? #=> true
frame.gone? #=> true
frame.data #=> nil
router_socket.send_message(reply) #=> raise exception, not allowed to send messages that have already been sent (and are gone)
- This change would probably affect every method of ZMQ::Message and ZMQ::Frame, so it'll be good to have some test coverage of our current use cases to make sure that implementing this doesn't break anything.
-
ZMQ::Frame could expose zero-copy allowing frames to be reused. Although changing the zero-copy status of the frame could be problematic. (Perhaps this could "freeze" the frame so that becomes permanently owned by ruby and the zero-copy state cannot be cleared...?) Actually, it appears that zmsg will still take ownership of a zero-copy frame, and destroy it from it's destroy method; however the message will relinquish ownership of a zero-copy frame when it is sent.
-
Reimplement the ruby ZMQ::Message class to use a ruby array as the frame store instead of zmsg and its zlist. This ruby array could be exposed to ruby so that it enumerated, modified, etc in place. When a message is sent, all items in the array would be coerced into zframes (eg: by
ZMQ::Frame.new(item.to_s)
) For example:
msg = ZMQ::Message.new
msg.frames << ZMQ::Frame("hello")
msg.frames << "there"
msg.frames #=> Array: [ <ZMQ::Frame>, <String> ]
socket.send_message msg
msg.gone? #=> true
msg.frames #=> nil
I'm pretty new to writing C extensions for ruby so I don't know how easy this would be to achieve... And it could be a lot of work for not much benefit...
Most of the top bit is implemented in this commit:
mattconnolly/rbczmq@e9aaefb