[gnome-network]LibGNetwork



So I'm currently working on libgnetwork, as a rework of libgtcpsocket,
and I was thinking about the use of threads. In libgtcpsocket, we use 3
threads per-connection: main, connect-and-read-poll, and write:

(Main)
    ::connect-done
        1.) create Write Thread
    ::lookup-done
        1.) Create (Connect&Read Thread)
    ::send
        1.) Push data onto write_queue.
    ::recv
    ::closed

(Lookup Thread)
    1.) gethostbyname().
    2.) ::lookup-done.
    3.) Exit thread.

(Connect&Read Thread) -- created by lookup-done
    1.) connect() || accept ()
    2.) install thread mainloop && handle polling for reads
    3.) ::connect-done.

(Write Thread)
    1.) Pop from write_queue, send data.
    2.) Sleep till write_queue has more data.

(Connect&Read Thread) pushes incoming data onto signal_queue, and a 50ms
timeout in (Main) checks for this data to emit the "recv", "closed",
etc. signals.

(Write Thread) pops data from write_queue, writes it, then pushes a
"recv" signal onto signal_queue.

Obviously, this is more than evil, so I'm rewriting it.

Currently, my plan is to have this subclassing tree:

GNetworkConnectionIface
|   // Signals
|   ::received (conn, data, len)
|   ::sent (conn, data, len)
|   ::error (conn, GError *)
|
|   // Properties
|   "status"
|   "bytes-received"
|   "bytes-sent"
|   "buffer-size"
|
|   // Methods
|   void (*open) (conn)
|   void (*close) (conn)
|   void (*send) (conn, data, len)
|
+-- GNetworkSslConnectionIface
    |   // Properties
    |   "ssl-enabled"
    |   "ssl-mode" [anon,cert] // cert == X.509
    |   "ssl-hostname" // server-hostname for auto-selecting certs.
    |
    |   // User Functions
    |   accept_bad_cert_func func
    |
    +-- GNetworkTcpConnection
        "tcp-status"

So, the new design for GNetworkTcpConnection can be either:

Threaded version:
===
(Main)
    open
        1.) Do lookup
        2.) Setup GSource to watch signal_queue.
        3.) Create IO Thread.
    send
        1.) Push data onto send_queue.
        
(IO Thread)
    main
        1.) connect() || accept ().
        1.) Create GIOChannel || GNetworkIOChannelSsl
        2.) Setup GIOChannel watch.
        3.) Add send_queue_watch GSource.
    IOChannel watch:
        1.) Handle for condition, push signal onto signal_queue,
            or do some type of "threaded-received", where the
            "received" signal is emitted in the IO Thread.
    send_queue_watch:
        1.) Check send_queue, write data to IOChannel.
===

Another possible threaded version would have a multi-threaded GIOChannel
impl with locks & such that would let me ditch the send_queue. This is
similar to how Link (linc) does things:

(Main)
    open
        1.) Do lookup
        2.) Setup GSource to watch signal_queue.
        3.) Create IO Thread.
    send
        1.) Get lock on IOChannel.
        2.) Write data.

(IO Thread)
    main:
        1.) connect() || accept ()
        2.) Sleep until connect() is done.
        3.) Create GIOChannel || GNetworkIOChannelSsl
        4.) Setup GIOChannel watch.
    IOChannel watch:
        1.) Handle for condition, push signal onto signal_queue,
            or do some type of "threaded-received", where the
            "received" signal is emitted in the IO Thread.

The non-threaded alternative is:

(Main)
    open:
        1.) Do lookup
        2.) Setup GSource to watch signal_queue.
        3.) Create GIOChannel || GNetworkIOChannelSsl.
        4.) Setup IOChannel Watch
    send:
        1.) Write data to IOChannel.
    IOChannel watch:
        1.) Handle for condition, emit signals as needed.

Threaded Pros:
1.) You can allow the "received" signal to be emitted in a thread, which
would allow subclasses to handle incoming parsing in a separate thread
(based on a "received-thread" property on GNetworkTcpConnection).
2.) The DNS lookup(s) (if performed) are already threaded by necessity,
so GNetwork could keep a GThreadPool around and re-use threads.
3.) I don't need to use non-blocking sockets, which means I don't have
to worry about polling the socket if connect() returns EINPROGRESS --
it's all linear.
4.) It's relatively close to the implementation that is already there,
it's just using IOChannels and combining the Connect&Read/Write Threads.

Threaded Cons:
1.) Threaded "received" implementations need to handle their own locking
(not a huge deal, but a gotcha for new coders).
2.) Standard problems with threads.
3.) Non-blocking sockets do the same job w/o threads.
4.) Two GAsyncQueues & 2 timeout funcs for passing signals & writes
between the threads is not cute.

Alternative Threads Pros:
1.) Don't need send_queue or a timeout func in IO Thread.

Alternative Threads Cons:
1.) Eliminates only benefit of doing connect() in a thread: Threaded Pro
#3.
2.) Must handle locking for IOChannel.

Non-Threaded Pros:
1.) Relatively straightforward code in gnetwork-tcp-connection.c.
2.) No thread issues at all.

Non-Threaded Cons:
1.) Can't offload parsing into it's own thread.

I'm currently vaguely in favor of the threaded design, as doing parsing
in the main loop is vaguely unattractive to me, and in the common case
we'll already have a thread to re-use from the DNS lookup (I plan on
adding a thread pool for the DNS stuff anyways to help apps which do a
fair amount of DNS work). Plus I like threads for some reason (perhaps
I'm a massochist? :-)). However, the complexity of threads vs. simple
non-blocking sockets is giving me pause -- sooo, fire away :-).

-- 
Peace,

    Jim Cape
    http://ignore-your.tv

    "It is literally true that, like Christianity, Socialism
     has conquered the world by defeating itself."
        -- Alexander Berkman, ABC of Anarchism

Attachment: signature.asc
Description: This is a digitally signed message part



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