Hello,
Recently I've been trying to port my small notification system (which
I use for replacing polling when possible, e.g. when process is
disconnected from the notifier it polls for changes, when connected it
polls for a known state one last time and just waits for messages
instead, switching back to polling when disconnected for whatever
reason) server from Python+gevent (nothing fancy, it an extremely
simple piece of code, probably less than 100 lines of code) to Go
(just to get a proper feel for making that kind of service in go).
What I found, however, is that because go channels are either
unbuffered (always block), or buffered to a certain amount (block when
full), so very trivial implementations of such brokers pose a risk to
the servers. Let's suppose you have a list of subscriber channels, you
iterate over them and send a message to every one of them. No matter
how big a buffer size you chose (the bigger the size, the bigger the
waste), it's still theoretically possible for slow clients to grind
the whole system to a halt because one channel became full (e.g.
network got disconnected in a very bad way, connections left wide
open, timeouts relatively big), dropping messages is a violation of
the protocol, and disconnecting most of the clients just because
there's a sudden spike is equally bad. Basically, with trivial
channels of messages slow clients don't harm themselves, they harm
everyone else.
After some thought I managed to design it something like this:
https://gist.github.com/2714090
Basically, clients' inbox is a buffered channel with only 1 item, but
instead of storing individual messages, it stores a whole batch of
them. This way, when broker itself tries to take it, it either
succeeds and appends to it, or creates a new batch with one message,
and puts it back into the channel (which is guaranteed to be empty,
since broker is the only one writing to it). Finally broker never
blocks on sends, only on receives, almost like Queues in gevent. It
can even disconnect too slow clients after a certain time (instead of
abstract number of messages), saving memory on larger than necessary
buffers.
The question though, is how do others solve these kinds of problems in
Go? (obviously, if there have been channels with dynamic buffer size,
this wouldn't be a problem) My solution works, but it feels kind of
shaky, with restrictions like only the broker is ever allowed to write
to inbox channels, etc.
P.S. Another obvious solution of sending messages to other inboxes by
clients themselves wouldn't really work. Yes, slow receivers wouldn't
penalize the whole system anymore, but they would still penalize some
innocent senders, as they wouldn't be able to send until buffer is
empty enough on the other end again.