Re: Finding out the signal detail from a handler



"James M. Cape" <jcape ignore-your tv> writes:

> For the exact case of GObject::notify, you can use the GParamSpec*
> argument in the signal prototype to get that info... I *think* most
> other detailed signals somehow or other include the detail as an
> argument somewhere.

Hi James,

Thanks for the reply, which makes sense, and thanks for the wonderful
GNetwork Library.  :-)

It's funny, the reason I asked this was actually because I was fooling
around with GNetwork.  I was considering adding a detail to the
"received" signal, and then I got curious as to how you usually find out
what the detail was.  Though I haven't got any particularly clear ideas
yet, I might as well tell you about what I'm trying to do now that I
have the chance.

First, the use case:  I'm building a thin wrapper called Messenger
around GNetworkConnection to send and receive messages using a very
simple message protocol in which each message looks like this (where
selector is a string and body is an opaque blob):

              [message size][selector][0][body size][body]

The reason I'm doing this is to have an easy way to implement an
RPC-style thing.  (Arguably this *is* an RPC-style, thing, but it
doesn't serialize and unserialize data, etc.)

The logic in Messenger's signal handler for "received" looks like this:

  * If we got a complete message, and didn't have any internal buffers,
    emit a "message-receieved" signal, with the selector as detail, and
    parameters being the body and the body's size (and, according to
    your reply, also the selector).

  * If we didn't get a complete message, and didn't have any internal
    buffers, create internal buffers large enough to hold the fields
    that we *did* get (some part of), and copy the data there.

  * If we already had some internal buffers, remember where we left off
    and continue filling buffers, creating new ones as needed.  If we
    got enough data to complete the message, emit a "message-received"
    signal.

Now, I realize that efficiency is not your primary concern with this
library, nor in fact is it mine with what I'm doing.  But it seemed to
me that by pulling some of this bookkeping out of my signal handler and
up into the GNetwork Library, we could both make the GNetwork Library
significantly easier to use for similar applications (i.e., applications
that want to read X bytes of data), *and* increase the performance of
said applications.  In addition, I believe we could still retain most of
the beautiful simplicity of the GNetwork API.

As I see it, the essential problem is that there is no way to control
how much data a GNetworkConnection reads before it emits a "received"
signal.  I've thought about two ways to fix this, one quite serious and
one not quite so serious, but of course there might be other even better
ones I haven't thought about.


The buffer size solution (quite serious)
========================================

This solution requires two changes to the GNetworkConnection API:

  * Introduce a boolean property "await-full-buffer" that tells the
    connection not to emit a signal until its incoming buffer is full.

    This is relatively unproblematic.  In fact, I've implemented it and
    as far as I can tell it preserves both source and binary
    compatibility.  I haven't got around to testing it though, as the
    tests in libgnetwork/tests/ seem pretty outdated.  (They provoke
    numerous assertion failures and segfaults on the current CVS head.)

  * Make it possible to change the "buffer-size" property on-the-fly.
    (Although this should probably never deallocate memory --
    analogously to how GString works.)

    We obviously need to make sure this works in a deterministic
    fashion, and I was a little unsure of how to do this with all the
    signal emitting and stuff going on.  If someone changes the buffer
    size while the buffer is being filled in, an error should probably
    be produced.  However, I'm not sure whether there are any other
    problematic cases.  Perhaps you can find the time to help me?  :-)

With this in place, the logic in Messenger's signal handler could be
reduced to the following:

  * If the state is READ_MESSAGE, emit a "message-received" signal and
    set the connection's "buffer-size" property to 4 (the width of the
    message size field).

  * Otherwise, the state is READ_MESSAGE_SIZE, so set the connection's
    "buffer-size" property to the number we just receieved.

No more buffers or copying data!


The explicit receive solution (not quite so serious)
====================================================

I noted that when using "await-full-buffer", Messenger's signal handler
had become reduced to a simple state machine.  What if this bookkeping,
too, could be pulled into GNetwork?  I realized that it probably
shouldn't, but I wanted to know what it might look like.

The idea of this solution is to introduce a receive method, reminiscent
of the COME FROM command featured in INTERCAL.  We'll need to add a
boolean property too, "explicit-receives", that can be set upon creation
to turn on this behvaior.

The receive method takes two parameters, a byte count and a tag, and
adds that pair to what I call the ``receieve queue''.  If the queue was
previously empty, the method makes sure the incoming buffer is large
enough to hold the specified number of byte, and sets the connection to
start listening to the network.

When in "explicit-receives" mode, the connection appends any data it
gets from the network to the incoming buffer.  When the buffer is full,
it emits a "received" signal with the detail set to the tag that's first
in the queue.  (Also, according to your reply, we'd need to add a
parameter to the receieved signal to hold the tag.)  It then dequeues
the first item, and if that empties the queue, it might stop listening
to the network.

The logic in Messenger's signal handler now looks like this:

  * If we got a message size, receieve a message with this size.

  * Otherwise, we got a message, so emit a "message-receieved" signal
    and receieve another message size.


Apologies for the long message (no pun intended); I didn't realize I had
so much to say.  Anyway, I'd love to hear what you (and others) think
about this.  I'm attaching a patch against the current CVS head that
implements "await-full-buffer".

Oh, a few more things, before I forget.

  * I'm using Automake 1.8, so I had to create a symlink called
    `automake-1.7' to bootstrap.  Not sure how to best fix this.

  * My editor usually only fits 80 columns of code, so it's a little
    annoying that the GNetwork sources uses up to 100 columns.

    In some cases (like GType macros), the code isn't any more
    unreadable when wrapped at an arbitrary column, so it's not really
    worth it to fit them in 80 columns -- especially since that kind of
    code often isn't really meant to be read by humans anyway.

    However, in most cases the source could easily fit 80 columns
    without trouble; it's just a matter of breaking the lines.  I would
    be glad to go through all of the source files and change this.  I
    wouldn't do it unless you agreed to apply the patch, though, since I
    don't want to maintain my own personal branch that changes every
    third line in the entire source tree using a line-based diff.  :-)

  * The pattern `foo->_priv->baz' occurs extremely often, and `foo' is
    usually about 10 characters long.  Since it's very unlikely for
    `foo->_priv->baz' and `bar->_priv->baz' to occur in the same
    function, I like to define a variable

      FooPrivate *priv = foo->_priv;

    at the top of functions that uses `foo->_priv' a lot.  In fact, the
    attached patch does this to a few functions that I had to modify
    anyway.  Again, I'd be happy to go through all source files and
    perform this change where it makes sense if you agree.  (It's
    confusing to use this idiom in some large functions but not other
    large functions.)

  * Tab characters are used instead of eight spaces, except in the
    following cases:

      - In the /* **** ... Public API ... **** */-style headers.

      - In some source code in documentation comments.

      - In the marshal.{c,h} files (which of course are generated).

      - Around ten random places where you obviously meant to use tabs.

    Tabs are fine with me (although I personally never use them), but in
    my opinion there should either be no tabs, or no
    eight-consecutive-spaces, so that you can use your editor's tabify
    and untabify commands to make sure you didn't mess up some file.
    What's your opinion on this one?

  * Is there a mailing list or other public forum that's used for
    GNetwork discussion -- other than this one?

Again, thanks heaps for the awesome library.


Best regards,

--
Daniel Brockman
drlion deepwood net




[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]