This is a quick note about Citadel Server internals that records something which is on my mind today.  It can be digested as a point of interest or converted to technical documentation if anyone is interested.  It revolves around the database table called S_USETABLE, whose format is:

struct UseTable {
    char ut_msgid[SIZ];
    time_t ut_timestamp;
    };

What is the purpose of this seemingly innocuous list of what appears to be message IDs and time stamps?

It originated in the dialup era, when Citadel sites syndicated rooms by relaying messages from node to node.  In a densely peered network, if the same message arrived through more than one path, it was important to record the message in the database only once.  The first arrival of a message would record its identity in the Use Table, with a message ID like "12345@uncnsrd" along with the date and time.  If the same message arrived via another route, the message would be silently dropped, but the timestamp would be updated.  After a period of time (10 days in the current implementation), it is assumed that the message has made its way through the network, and the record is purged.

This heuristic is neatly composed by a single function CheckIfAlreadySeen() which is supplied the ID of a message, and returns a nonzero value if the ID is already in the database, zero if it is newly seen.  Either way, the current date and time are written to the table.  But why is it still there, when Citadel room sharing is a thing of the past?

Well, it turns out this is a very useful and simple way of ensuring that any event happens only once.  The RSS reader uses it to make sure we don't keep posting the same item from some remote feed over and over, for example:

StrBufAppendPrintf(u, "rss/%s", r->item_id);
int already_seen = CheckIfAlreadySeen(u);

And the POP3 client uses it to ensure that if we are downloading the contents of a remote mailbox somewhere, that we download each message only once:

StrBufPrintf(UT, "pop3/%s/%s:%s@%s", room, oneuidl, user, host);
int already_seen = CheckIfAlreadySeen(UT);

The reason I'm writing about the Use Table today is because I just thought of another place to use it.  We are currently overhauling our "inbox rules" module, and one job of the inbox rules handler is to send out auto-responder messages when a user requests that action.  But we don't want to keep sending out auto-responses over and over again, only once per recipient.  In the old code we did this by recording the email address of each correspondent to whom we sent the message, along with a datestamp, directly in the inbox rules record for the user.  This required rewriting the ruleset over and over again.   Clearly, this is a job better suited to the Use Table.  All we have to do is make up a hash of the local user's identity, the email address of the correspondent, and perhaps a hash of the outbound auto-response so that the record can be invalidated if the message changes.  This goes into the Use Table, et voilà, the auto-response only goes out once per recipient.  If a particular correspondent is idle for ten days, and sends another message to our Citadel user while the auto-responder is still in effect, they will get a new copy of the auto-response, which seems reasonable.

This "feels" a lot better than the old implementation, and I'm quite happy with it.





Reply via email to