[nanomsg] Re: On the use of small integers instead of pointers to identify "sockets"

  • From: Martin Sustrik <sustrik@xxxxxxxxxx>
  • To: nanomsg@xxxxxxxxxxxxx
  • Date: Wed, 04 Sep 2013 06:49:16 +0200

Hi Nico,

The socket handles are ints to comply with POSIX specification.

In case you need type-safe socket handles, it's easy to layer that on top of nanomsg. Actually, most language bindings are doing so.

Btw, one minor, but nice feature of ints as socket handles is that you can close a socket, then try to use it and get EBADF error. In ZeroMQ, where I've used pointers as handles, your program just crashes.

Martin

On 03/09/13 23:32, Nico Wiliams wrote:
Hi,

I read a blog post about nanomsg's design, linked from HN.

I was quite pleased with everything I read except the use of small
integers to identify nanomsg sockets, and the lack of any context
handles.

Small integers / no context handles -> easy for developers using
nanomsg, because it's familiar from the BSD sockets API.  But it's not
really any more difficult to use an opaque handle instead: instead of
'int' use an incomplete struct pointer typedef as follows

typdef struct nn_handle *nn_handle;

NN_EXPORT nn_handle nn_socket (int domain, int protocol);
NN_EXPORT int nn_close (nn_handle s);
NN_EXPORT int nn_setsockopt (nn_handle s, int level, int option, const
void *optval,
     size_t optvallen);
...

The only change then is the type of 's' in these functions and the
return value type for nn_socket().

Oh, and static type safety: there will be no way (short of casting) to
get past a compiler error if one accidentally mixes real file
descriptors with nn descriptors.

Developers writing nanomsg apps should not really notice the
difference.  It's just a type name difference that changes nothing
about how the values of that type are used.  The 'nn_handle' type
could even be a typedef of int (typedef int nn_handle).

Given that the nn sockets are opaque anyways, why should the
underlying type matter to nn callers?  How can using an integer type
help a developer in this case?

Small integers imply a table lookup that is difficult to make fast and
thread-safe.  In an API where there is isolation between the caller
and the callee (e.g., system calls) this is a good thing to have as a
validation mechanism: kernels shouldn't dereference kernel memory
addresses supplied by user-land callers!  Integers still aren't needed
for this, but it is easier to validate small integers than arbitrary
pointers[*].

Looking at the code... right now nanomsg depends on global locks a bit
too much, IMO, and much of this dependency has to do with the
self.socks[] array initialization (and initialization and maintenance
of self.nsocks, and self.unused, ...).  At least some of which would
go away if you used pointers instead of small integers to name nn
sockets.  Some of this could be optimized by using pthread_once() for
initialization and judicious use of memory barriers and other
lighter-weight synchronization primitives, but it's not trivial to get
right.  Whereas the socktypes and transports lists could be trivially
initialized once in a pthread_once(), with no further synchronization
being required (except for nn_term(), but I wonder if it's really
desirable to allow applications to call nn_term(); seems like a job
for .fini sections).

Also there's a hard max on number of sockets, which can only be
removed by either complicating the table lookup and/or lookup table
management... or by not having to have a table in the first place.

Respectfully,

Nico

[*]  Note that a kernel-mode nanomsg implementation could use small
integers cast back and forth to an opaque struct pointer anyways, so
all in all I don't see how naming sockets after small integers helps.



Other related posts: