Skip to content

Instantly share code, notes, and snippets.

@mattconnolly
Last active December 19, 2015 13:38
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mattconnolly/5963270 to your computer and use it in GitHub Desktop.
Save mattconnolly/5963270 to your computer and use it in GitHub Desktop.
rbczmq notes

Object Ownership:

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 the zmsg_t.
  • Calling zmsg_add means that the zmsg_t takes ownership of the zframe_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 message
  • zmq_frame_wrapper would also have a pointer to the zmq_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.

Other less important ideas:

  • 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...

@mattconnolly
Copy link
Author

Most of the top bit is implemented in this commit:

mattconnolly/rbczmq@e9aaefb

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment