Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for nng_msg as Message #24

Merged
merged 9 commits into from
Jan 21, 2019
Merged

Support for nng_msg as Message #24

merged 9 commits into from
Jan 21, 2019

Conversation

codypiersall
Copy link
Owner

This is the beginning of supporting nng_msg in pynng. Related issue is #21.

So far only sychronous methods are supported. The API will change, I think, before this is merged; I think that my Message class will grow a public send() method. Instead of socket.sendmsg(msg), you would instead call msg.send(). Feedback welcome. To see how it's used right now, check out the tests!

Copy link
Contributor

@goodboy goodboy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lookin pretty good to me 👍

pynng/nng.py Outdated
pipe_id = lib.nng_pipe_id(lib_pipe)
if pipe_id < 0:
# TODO: Better exception
raise Exception('No such pipe')
Copy link
Contributor

@goodboy goodboy Jan 13, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about PipeNotFoundError much like ModuleNotFoundError?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First I want to check to see what nng itself does whenever it's given a pipe object that doesn't exist anymore, and see if it makes sense to reuse that exception; otherwise, I like this.

test/test_msg.py Outdated
to = 1000


def test_socket_send_recv_msg():
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like it.

test/test_msg.py Outdated
msg = pipe.new_msg(b'oh hello friend')
assert isinstance(msg, pynng.Message)
assert msg.bytes == b'oh hello friend'
s1.sendmsg(msg)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm slightly partial to send_msg() / recv_msg() personally.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In my first version, I did use send_msg and recv_msg; I changed it to be more consistent with nng. But you convinced me! I'm going to change it back.

test/test_msg.py Outdated
wait_pipe_len(s1, 1)
pipe = s1.pipes[0]
msg = pipe.new_msg(b'oh hello friend')
assert msg.pipe is pipe
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like you could probably just stick these 2 extra pipe assertions in the above test to save some duplication?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably worth doing. I was trying to narrow the scope of tests, but whenever the test is almost completely duplicated anyway it makes sense to put more into it.

pynng/nng.py Outdated
flags = 0
if not block:
flags |= lib.NNG_FLAG_NONBLOCK
lib_sock = self.pipe.socket.socket
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm I wonder if it should be Socket.nng_socket (kinda out of scope)..

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I'm pretty unhappy with that on all the pynng right now. Listener has a listener attribute for the underlying nng object, Dialer has a dialer attribute, etc. I want to change it from what it is now, but am not sure whether to change it to either the nng library name (so nng_socket like you suggested here) or give every object a _lib_obj attribute that is the nng object.

I'm leaning toward the latter, but I'm not confident it's better. Feedback is welcome there!

Copy link
Contributor

@goodboy goodboy Jan 13, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah that seems fine.
The only thing I can say helps me is some prefix that indicates what is ffi pointers / refs versus native python objects.

@codypiersall
Copy link
Owner Author

I was super wrong for giving the Message a send() method. I was thinking that a message would always need to have a pipe associated, but that's not the case. For example, it may not even be supported to set the pipe for pub/sub. So now socket will have a send_msg() method again.

@codypiersall
Copy link
Owner Author

Most of the functionality is ironed out now, but the API is not really ready yet; mostly, I think I'll remove the new_msg() methods on Pipe, Socket, and Context, and just let people call Message() directly.

Additionally, I need to not do raise Exception() like I'm doing now, and I should think about adding threading.Lock() objects to Message to atomically check the _sent flag to prevent potential segfaults. I guess uncontested locks should be fast enough; on my computer it takes about 0.133 us to acquire and release a lock. (Command was python -m timeit -s 'from threading import Lock; l = Lock()' 'with l: pass')

So a little left to do on this PR, but not too much, I think.

Feedback welcome!

@codypiersall codypiersall changed the title WIP: Support for Message. Synchronous version works. API will change. Do not merge. Support for nng_msg as Message Jan 21, 2019
@codypiersall codypiersall merged commit 0a8e4b1 into master Jan 21, 2019
Copy link
Contributor

@goodboy goodboy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just realized this was already merged but figured I'd give it a once over anyway.
Had my head in my own pile of code this weekend, heh.

if pipe_id < 0:
# TODO: Better exception
raise Exception('No such pipe')
pipe = self._pipes[pipe_id]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return self._pipes[pipe_id] ?

with msg._mem_freed_lock:
msg._ensure_can_send()
with _aio.AIOHelper(self, self._async_backend) as aio:
val = await aio.asend_msg(msg)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return await aio.asend_msg(msg)

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1, this was cruft leftover from an earlier round of coding.

msg._ensure_can_send()
with _aio.AIOHelper(self, self._socket._async_backend) as aio:
val = await aio.asend_msg(msg)
return val
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

again just a single line seems fine.


def __init__(self, data, pipe=None):
self._pipe = pipe
# NB! There are two ways that a user can free resources that an nng_msg
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment might be out of date now?
Re:

So the only way to send a message is with the send() and asend() methods on the object.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good observation.

"""

def __init__(self, data, pipe=None):
self._pipe = pipe
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe this should use the setter property?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updating this also showed me a bug in my code: I thought you could "unset" a message's pipe by passing NULL, but I never tested it, and it caused problems. (I'm not sure why I ever thought that would be possible; nng_msg_set_pipe doesn't take a pointer, it takes a nng_pipe... oh well!)

self._pipe = None
return
if not isinstance(pipe, Pipe):
msg = 'pipe must be type Pipe, not {}'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

msg = 'pipe must be type Pipe, not {}'.format(type(pipe))

I mean heck you could do it all in the ValueError arg.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1

pynng/nng.py Outdated
self._pipe = pipe

@property
def buffer(self):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it maybe be handy to make this a context manager that will block you from being able to send while the buffer is in use?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had the same idea, but I couldn't come up with an implementation that actually guaranteed preventing using a buffer after sending. Once the user has access to the buffer, there's no way to disable using it. Imagine code like this:

with msg.borrow_buffer() as buf:
    do_something_cool(buf)
sock.send_msg(msg)
# can't prevent this
print(buf[0:10])

There is no way to mark a buffer as being invalid. This is a cffi.buffer type. Maybe it's not worth exposing it at all though? As far as I can tell this is the only place in the library where it would be pretty easy to cause a segfault. I named it _buffer with an underscore to indicate that it's dangerous, not that it's private; hopefully people would mostly reach for the bytes property...

if not self._mem_freed:
size = lib.nng_msg_len(self._nng_msg)
data = ffi.cast('char *', lib.nng_msg_body(self._nng_msg))
return ffi.buffer(data[0:size])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you need the 0?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, for ffi.buffer objects the start index has to be given.

@codypiersall
Copy link
Owner Author

Thanks for the feedback @tgoodlet! I'll look at it all soon.

codypiersall added a commit that referenced this pull request Jan 21, 2019
Code cleanup based on review in
#24 by @tgoodlet.
@codypiersall codypiersall deleted the nng_msg2 branch January 26, 2019 22:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants