mirror of
https://github.com/unrealircd/unrealircd.git
synced 2026-06-12 19:34:47 +02:00
Optimize multiline delivery to channels (use LineCache)
This wasn't done before, because optimizing stuff can always introduce nice new issues. But is kinda necessary now since the previous way was very inefficient. This now builds all the necessary buffers for multiline clients and for non-multiline clients. And then iterates through both types of clients, sending what they need. Instead of doing it the other way around. I had the dillema to either expose the linecache API and have everything in multiline.c. Or, i do not expose linecache, and we do everything in send.c. The downside of the latter is that if there is mistake then we can't simply reload (or unload) the module to solve it. So, I have chosen to expose the linecache API (sure, less clean) since that leaves us with options if we screw up, plus it means everything related to multiline sending is nicely in multiline.c, which is i guess just as good as an argument as well ;)
This commit is contained in:
@@ -285,6 +285,11 @@ extern int send_queued(Client *);
|
||||
extern void send_queued_cb(int fd, int revents, void *data);
|
||||
extern void sendto_serv_butone_nickcmd(Client *one, MessageTag *mtags, Client *client, const char *umodes);
|
||||
extern void sendto_message_one(Client *to, Client *from, const char *sender, const char *cmd, const char *nick, const char *msg);
|
||||
extern LineCache *linecache_init(void);
|
||||
extern void linecache_free(LineCache *cache);
|
||||
extern void sendto_prefix_one_cached(LineCache *cache, int line_opts, Client *to, Client *from,
|
||||
MessageTag *mtags,
|
||||
FORMAT_STRING(const char *pattern), ...) __attribute__((format(printf,6,7)));
|
||||
extern void sendto_channel(Channel *channel, Client *from, Client *skip,
|
||||
char *member_modes, long clicap, int sendflags,
|
||||
MessageTag *mtags,
|
||||
|
||||
@@ -147,6 +147,10 @@ typedef struct LocalMember LocalMember;
|
||||
typedef struct OutgoingWebRequest OutgoingWebRequest;
|
||||
typedef struct OutgoingWebResponse OutgoingWebResponse;
|
||||
|
||||
typedef enum LineCacheUserType { LCUT_NORMAL=0, LCUT_OPER=1, LCUT_REMOTE=2 } LineCacheUserType;
|
||||
typedef struct LineCacheLine LineCacheLine;
|
||||
typedef struct LineCache LineCache;
|
||||
|
||||
typedef enum OperClassEntryType { OPERCLASSENTRY_ALLOW=1, OPERCLASSENTRY_DENY=2} OperClassEntryType;
|
||||
|
||||
typedef enum OperPermission { OPER_ALLOW=1, OPER_DENY=0} OperPermission;
|
||||
@@ -1609,6 +1613,21 @@ struct MessageTag {
|
||||
char *value;
|
||||
};
|
||||
|
||||
struct LineCacheLine
|
||||
{
|
||||
LineCacheLine *prev, *next;
|
||||
LineCacheUserType user_type;
|
||||
unsigned long caps;
|
||||
int line_opts;
|
||||
char *line;
|
||||
int linelen;
|
||||
};
|
||||
|
||||
struct LineCache
|
||||
{
|
||||
LineCacheLine *items;
|
||||
};
|
||||
|
||||
/* conf preprocessor */
|
||||
typedef enum PreprocessorItem {
|
||||
PREPROCESSOR_ERROR = 0,
|
||||
|
||||
+138
-11
@@ -1371,7 +1371,10 @@ static void multiline_echo_to_sender(Client *client, MultilineBatch *batch,
|
||||
|
||||
/* ===================== SENDING HELPERS ===================== */
|
||||
|
||||
/** Send a multiline batch to a local client that supports draft/multiline */
|
||||
/** Send a multiline batch to a single local client that supports draft/multiline.
|
||||
* Used for user-to-user delivery, echo-message, and S2S incoming.
|
||||
* For channel broadcast, use multiline_deliver_to_local_members() instead.
|
||||
*/
|
||||
static void multiline_send_batch_to_client(Client *to, Client *from, MultilineBatch *batch,
|
||||
MessageTag *base_mtags, const char *targetstr, const char *cmd)
|
||||
{
|
||||
@@ -1417,7 +1420,10 @@ static void multiline_send_batch_to_client(Client *to, Client *from, MultilineBa
|
||||
sendto_prefix_one(to, from, NULL, ":%s BATCH -%s", from->name, server_batch_id);
|
||||
}
|
||||
|
||||
/** Send multiline as individual fallback lines to a local client */
|
||||
/** Send multiline as individual fallback lines to a single local client.
|
||||
* Used for user-to-user delivery, echo-message, and S2S incoming.
|
||||
* For channel broadcast, use multiline_deliver_to_local_members() instead.
|
||||
*/
|
||||
static void multiline_send_fallback_to_client(Client *to, Client *from, MultilineBatch *batch,
|
||||
MessageTag *base_mtags, const char *targetstr, const char *cmd)
|
||||
{
|
||||
@@ -1460,35 +1466,156 @@ static void multiline_send_fallback_to_client(Client *to, Client *from, Multilin
|
||||
}
|
||||
|
||||
/** Deliver multiline batch to local channel members.
|
||||
* Iterates local_members, applies STATUSMSG filtering, skips sender and deaf
|
||||
* as requested, and dispatches via batch or fallback depending on capability.
|
||||
* Uses two passes (multiline-capable, then fallback) with pre-built
|
||||
* message tags. This uses LineCache, which makes this function
|
||||
* a bit complex but saves A LOT of CPU so is necessary.
|
||||
* @param skip Client to skip (sender for local delivery, NULL for S2S)
|
||||
* @param sendflags Send flags (SKIP_DEAF etc.), 0 if not applicable
|
||||
*/
|
||||
static void multiline_deliver_to_local_members(Channel *channel, Client *from,
|
||||
MultilineBatch *batch, MessageTag *mtags, const char *targetstr,
|
||||
MultilineBatch *batch, MessageTag *base_mtags, const char *targetstr,
|
||||
const char *cmd, const char *filter_modes, Client *skip, int sendflags)
|
||||
{
|
||||
LocalMember *lm;
|
||||
MLine *l;
|
||||
MLine *fallback_lines; // merged lines for non-multiline clients
|
||||
MessageTag *m; // temporary for building tag lists
|
||||
MessageTag *rest_mtags; // base_mtags minus first-only tags (msgid, +draft/reply)
|
||||
MessageTag *mtags_line; // rest_mtags + batch=<id>, for multiline content lines
|
||||
MessageTag *mtags_line_concat; // mtags_line + draft/multiline-concat
|
||||
LineCache *cache_batch_open; // cache for "BATCH +id draft/multiline target"
|
||||
LineCache *cache_batch_close; // cache for "BATCH -id"
|
||||
LineCache **cache_lines = NULL; // one cache per multiline content line
|
||||
LineCache **cache_fb = NULL; // one cache per non-blank fallback line
|
||||
char server_batch_id[BATCHLEN+1]; // shared across all multiline recipients
|
||||
int i;
|
||||
int fallback_count = 0; // number of non-blank fallback lines
|
||||
int first; // tracks first non-blank line in fallback pass
|
||||
|
||||
/* === Phase 1: Pre-build shared resources (once, not per-client) === */
|
||||
|
||||
/* Single batch ID shared across all multiline-capable recipients.
|
||||
* Safe: each client connection tracks batches independently.
|
||||
*/
|
||||
generate_batch_id(server_batch_id);
|
||||
|
||||
/* rest_mtags: base_mtags minus first-only tags (msgid, +draft/reply).
|
||||
* Used for subsequent lines in both multiline and fallback paths.
|
||||
*/
|
||||
rest_mtags = duplicate_mtags_for_subsequent_lines(base_mtags);
|
||||
|
||||
/* mtags_line: rest_mtags + batch=<id>, for non-concat multiline lines */
|
||||
mtags_line = duplicate_mtags(rest_mtags);
|
||||
m = safe_alloc(sizeof(MessageTag));
|
||||
safe_strdup(m->name, "batch");
|
||||
safe_strdup(m->value, server_batch_id);
|
||||
AddListItem(m, mtags_line);
|
||||
|
||||
/* mtags_line_concat: mtags_line + draft/multiline-concat */
|
||||
mtags_line_concat = duplicate_mtags(mtags_line);
|
||||
m = safe_alloc(sizeof(MessageTag));
|
||||
safe_strdup(m->name, "draft/multiline-concat");
|
||||
AddListItem(m, mtags_line_concat);
|
||||
|
||||
/* === Phase 2: Allocate LineCaches === */
|
||||
|
||||
/* Multiline path: BATCH open + N content lines + BATCH close */
|
||||
cache_batch_open = linecache_init();
|
||||
cache_batch_close = linecache_init();
|
||||
if (batch->line_count > 0)
|
||||
cache_lines = safe_alloc(sizeof(LineCache *) * batch->line_count);
|
||||
for (i = 0; i < batch->line_count; i++)
|
||||
cache_lines[i] = linecache_init();
|
||||
|
||||
/* Fallback path: one cache per non-blank fallback line */
|
||||
fallback_lines = multiline_get_fallback_lines(batch);
|
||||
for (l = fallback_lines; l; l = l->next)
|
||||
if (l->text && l->text[0])
|
||||
fallback_count++;
|
||||
if (fallback_count > 0)
|
||||
cache_fb = safe_alloc(sizeof(LineCache *) * fallback_count);
|
||||
for (i = 0; i < fallback_count; i++)
|
||||
cache_fb[i] = linecache_init();
|
||||
|
||||
/* === Phase 3: Multiline pass === */
|
||||
for (lm = channel->local_members; lm; lm = lm->next)
|
||||
{
|
||||
Client *acptr = lm->ptr->client;
|
||||
|
||||
if (acptr == skip)
|
||||
continue;
|
||||
|
||||
if (IsDeaf(acptr) && (sendflags & SKIP_DEAF))
|
||||
continue;
|
||||
|
||||
if (filter_modes && !check_channel_access_member(lm->ptr, filter_modes))
|
||||
continue;
|
||||
if (!HasMultiline(acptr))
|
||||
continue;
|
||||
|
||||
if (HasMultiline(acptr))
|
||||
multiline_send_batch_to_client(acptr, from, batch, mtags, targetstr, cmd);
|
||||
else
|
||||
multiline_send_fallback_to_client(acptr, from, batch, mtags, targetstr, cmd);
|
||||
/* BATCH open */
|
||||
sendto_prefix_one_cached(cache_batch_open, 0, acptr, from, base_mtags,
|
||||
":%s BATCH +%s draft/multiline %s",
|
||||
from->name, server_batch_id, targetstr);
|
||||
|
||||
/* Content lines */
|
||||
i = 0;
|
||||
for (l = batch->lines; l; l = l->next, i++)
|
||||
{
|
||||
sendto_prefix_one_cached(cache_lines[i], 0, acptr, from,
|
||||
l->concat ? mtags_line_concat : mtags_line,
|
||||
":%s %s %s :%s",
|
||||
from->name, cmd, targetstr, l->text ? l->text : "");
|
||||
}
|
||||
|
||||
/* BATCH close */
|
||||
sendto_prefix_one_cached(cache_batch_close, 0, acptr, from, NULL,
|
||||
":%s BATCH -%s", from->name, server_batch_id);
|
||||
}
|
||||
|
||||
/* === Phase 4: Fallback pass === */
|
||||
for (lm = channel->local_members; lm; lm = lm->next)
|
||||
{
|
||||
Client *acptr = lm->ptr->client;
|
||||
|
||||
if (acptr == skip)
|
||||
continue;
|
||||
if (IsDeaf(acptr) && (sendflags & SKIP_DEAF))
|
||||
continue;
|
||||
if (filter_modes && !check_channel_access_member(lm->ptr, filter_modes))
|
||||
continue;
|
||||
if (HasMultiline(acptr))
|
||||
continue;
|
||||
|
||||
first = 1;
|
||||
i = 0;
|
||||
for (l = fallback_lines; l; l = l->next)
|
||||
{
|
||||
/* Spec: "Servers MUST NOT send blank lines to clients
|
||||
* that have not negotiated the multiline capability."
|
||||
*/
|
||||
if (!l->text || !l->text[0])
|
||||
continue;
|
||||
|
||||
sendto_prefix_one_cached(cache_fb[i], 0, acptr, from,
|
||||
first ? base_mtags : rest_mtags,
|
||||
":%s %s %s :%s",
|
||||
from->name, cmd, targetstr, l->text);
|
||||
first = 0;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
/* === Phase 5: Cleanup === */
|
||||
linecache_free(cache_batch_open);
|
||||
linecache_free(cache_batch_close);
|
||||
for (i = 0; i < batch->line_count; i++)
|
||||
linecache_free(cache_lines[i]);
|
||||
safe_free(cache_lines);
|
||||
for (i = 0; i < fallback_count; i++)
|
||||
linecache_free(cache_fb[i]);
|
||||
safe_free(cache_fb);
|
||||
free_message_tags(rest_mtags);
|
||||
free_message_tags(mtags_line);
|
||||
free_message_tags(mtags_line_concat);
|
||||
}
|
||||
|
||||
/** Send a multiline batch (BATCH open + lines + BATCH close) to a single server direction */
|
||||
|
||||
+20
-24
@@ -26,34 +26,12 @@
|
||||
|
||||
#include "unrealircd.h"
|
||||
|
||||
/* Local structs */
|
||||
typedef enum LineCacheUserType { LCUT_NORMAL=0, LCUT_OPER=1, LCUT_REMOTE=2 } LineCacheUserType;
|
||||
|
||||
typedef struct LineCacheLine LineCacheLine;
|
||||
struct LineCacheLine
|
||||
{
|
||||
LineCacheLine *prev, *next;
|
||||
LineCacheUserType user_type;
|
||||
unsigned long caps;
|
||||
int line_opts; /**< Cached line message options (rare) */
|
||||
char *line; /**< Entire cached line, including message tags (if appropriate) and \r\n */
|
||||
int linelen; /**< strlen(line) */
|
||||
};
|
||||
|
||||
typedef struct LineCache LineCache;
|
||||
struct LineCache
|
||||
{
|
||||
// later: possible options?
|
||||
LineCacheLine *items;
|
||||
};
|
||||
|
||||
/* Some forward declarions are needed */
|
||||
void vsendto_one(Client *to, MessageTag *mtags, const char *pattern, va_list vl);
|
||||
void vsendto_prefix_one(Client *to, Client *from, MessageTag *mtags, const char *pattern, va_list vl) __attribute__((format(printf,4,0)));
|
||||
static int vmakebuf_local_withprefix(char *buf, size_t buflen, Client *from, const char *pattern, va_list vl) __attribute__((format(printf,4,0)));
|
||||
static void vsendto_prefix_one_cached(LineCache *cache, int line_opts, Client *to, Client *from, MessageTag *mtags, const char *pattern, va_list vl) __attribute__((format(printf,6,0)));
|
||||
static LineCache *linecache_init(void);
|
||||
static void linecache_free(LineCache *cache);
|
||||
static void linecache_add(LineCache *cache, int line_opts, Client *to, const char *line, int linelen);
|
||||
static LineCacheLine *linecache_get(LineCache *cache, int line_opts, Client *to);
|
||||
|
||||
@@ -1104,13 +1082,13 @@ void vsendto_prefix_one(Client *to, Client *from, MessageTag *mtags, const char
|
||||
}
|
||||
}
|
||||
|
||||
static LineCache *linecache_init(void)
|
||||
LineCache *linecache_init(void)
|
||||
{
|
||||
LineCache *e = safe_alloc(sizeof(LineCache));
|
||||
return e;
|
||||
}
|
||||
|
||||
static void linecache_free(LineCache *cache)
|
||||
void linecache_free(LineCache *cache)
|
||||
{
|
||||
LineCacheLine *e, *e_next;
|
||||
for (e = cache->items; e; e = e_next)
|
||||
@@ -1215,6 +1193,24 @@ static void vsendto_prefix_one_cached(LineCache *cache, int line_opts, Client *t
|
||||
}
|
||||
}
|
||||
|
||||
/** Cached version of sendto_prefix_one() - varargs wrapper.
|
||||
* @param cache The LineCache to use
|
||||
* @param line_opts LineCache options for this particular message/line (usually 0)
|
||||
* @param to The client to send to
|
||||
* @param from The sender
|
||||
* @param mtags Any message tags associated with this message (can be NULL)
|
||||
* @param pattern The format string / pattern to use.
|
||||
* @param ... Format string parameters.
|
||||
*/
|
||||
void sendto_prefix_one_cached(LineCache *cache, int line_opts, Client *to, Client *from,
|
||||
MessageTag *mtags, FORMAT_STRING(const char *pattern), ...)
|
||||
{
|
||||
va_list vl;
|
||||
va_start(vl, pattern);
|
||||
vsendto_prefix_one_cached(cache, line_opts, to, from, mtags, pattern, vl);
|
||||
va_end(vl);
|
||||
}
|
||||
|
||||
/** Introduce user to all other servers, except the one to skip.
|
||||
* @param one Server to skip (can be NULL)
|
||||
* @param client Client to introduce
|
||||
|
||||
Reference in New Issue
Block a user