diff --git a/src/ckdb.c b/src/ckdb.c index 197eda34..5c77f970 100644 --- a/src/ckdb.c +++ b/src/ckdb.c @@ -1689,7 +1689,7 @@ static void trans_seq(tv_t *now) static bool update_seq(enum seq_num seq, uint64_t n_seqcmd, uint64_t n_seqstt, uint64_t n_seqpid, char *nam, tv_t *now, tv_t *cd, char *code, - bool warndup, char *msg) + int seqitemflags, char *msg) { char t_buf[DATE_BUFSIZ], t_buf2[DATE_BUFSIZ], *st = NULL; bool firstseq, newseq, expseq, gothigh, gotstale, gotstalestart; @@ -1697,13 +1697,13 @@ static bool update_seq(enum seq_num seq, uint64_t n_seqcmd, SEQSET seqset_exp = { 0 }, seqset_copy = { 0 }; bool dup, wastrans, doitem, dotime; SEQDATA *seqdata; - SEQITEM *seqitem; + SEQITEM *seqitem, seqitem_copy; K_ITEM *seqset_item = NULL, *st_item = NULL; SEQTRANS *seqtrans = NULL; size_t siz, end; void *off0, *offn; uint64_t u; - int set = -1, highlimit, i; + int set = -1, expset = -1, highlimit, i; K_STORE *lost; // We store the lost items in here @@ -1732,10 +1732,10 @@ static bool update_seq(enum seq_num seq, uint64_t n_seqcmd, } } - // Need to get a new seqset + // Need to setup a new seqset newseq = true; if (!firstseq) { - /* The current seqset (about to become the previous) + /* The current seqset (may become the previous) * If !seqset_store->head (i.e. a bug) this will quit() */ DATA_SEQSET(seqset0, seqset_store->head); memcpy(&seqset_pre, seqset0, sizeof(seqset_pre)); @@ -1796,16 +1796,59 @@ static bool update_seq(enum seq_num seq, uint64_t n_seqcmd, LIST_MEM_ADD_SIZ(seqset_free, end); } } else { - // Expire the last set and overwrite it - seqset_item = k_unlink_tail(seqset_store); - // If !item (i.e. a bug) this will quit() + // Expire the oldest set and overwrite it + K_ITEM *ss_item; + SEQSET *ss = NULL; + int s = 0; + seqset = NULL; + seqset_item = NULL; + ss_item = seqset_store->head; + while (ss_item) { + DATA_SEQSET(ss, ss_item); + if (!seqset) { + seqset = ss; + seqset_item = ss_item; + expset = s; + } else { + // choose the last match + if (ss->seqstt >= seqset->seqstt) { + seqset = ss; + seqset_item = ss_item; + expset = s; + } + } + ss_item = ss_item->next; + s++; + } + // If !seqset_item (i.e. a bug) k_unlink_item() will quit() + k_unlink_item(seqset_store, seqset_item); DATA_SEQSET(seqset, seqset_item); memcpy(&seqset_exp, seqset, sizeof(seqset_exp)); expseq = true; RESETSET(seqset, n_seqstt, n_seqpid); } - k_add_head(seqset_store, seqset_item); - set = 0; + /* Since the pool queue is active during the reload, sets can be out + * of order, so each new one should be added depending upon the value + * of seqstt so the current pool is first, to minimise searching + * seqset_store, but the order of the rest isn't as important + * N.B. a new set is only created once per pool start */ + if (firstseq) { + k_add_head(seqset_store, seqset_item); + set = 0; + } else { + // seqset0 already is the head + if (n_seqstt >= seqset0->seqstt) { + // if new set is >= head then make it the head + k_add_head(seqset_store, seqset_item); + set = 0; + } else { + // put it next after the head + k_insert_after(seqset_store, seqset_item, + seqset_store->head); + set = 1; + } + + } gotseqset: doitem = dotime = false; @@ -1895,6 +1938,7 @@ gotseqset: if (!ITEMISMIS(seqitem)) { dup = true; memcpy(&seqset_copy, seqset, sizeof(seqset_copy)); + memcpy(&seqitem_copy, seqitem, sizeof(seqitem_copy)); } else { // Found a missing one seqdata->missing--; @@ -1942,6 +1986,7 @@ gotseqset: setitemdata: // Store the new seq if flagged to do so if (doitem) { + seqitem->flags = seqitemflags; copy_tv(&(seqitem->time), now); copy_tv(&(seqitem->cd), cd); STRNCPY(seqitem->code, code); @@ -1969,31 +2014,37 @@ setitemdata: nam, n_seqcmd, n_seqstt, t_buf, n_seqpid); } else { if (newseq) { - // previous set is set 1 - SEQSETWARN(1, &seqset_pre, "previous", EMPTY); - } - if (expseq) { - // set -1 means it was the discarded/removed last set - SEQSETWARN(-1, &seqset_exp, "discarded old", " for:"); + if (set == 0) + SEQSETWARN(0, &seqset_pre, "previous", EMPTY); + else + SEQSETWARN(0, &seqset_pre, "current", EMPTY); } + if (expseq) + SEQSETWARN(expset, &seqset_exp, "discarded old", " for:"); if (newseq || expseq) { btu64_to_buf(&n_seqstt, t_buf, sizeof(t_buf)); - LOGWARNING("Seq created new: %s %"PRIu64" " + LOGWARNING("Seq created new: set %d %s %"PRIu64" " SEQSTT" %"PRIu64"=%s "SEQPID" %"PRIu64, - nam, n_seqcmd, n_seqstt, t_buf, n_seqpid); + set, nam, n_seqcmd, n_seqstt, t_buf, + n_seqpid); } } if (dup) { - int level = LOG_DEBUG; - if (warndup) - level = LOG_WARNING; + int level = LOG_WARNING; + /* If one is SI_RELOAD and the other is SI_EARLYSOCK then it's + * not unexpected so only LOG_DEBUG */ + if (((seqitem_copy.flags | seqitemflags) & SI_RELOAD) && + ((seqitem_copy.flags | seqitemflags) & SI_EARLYSOCK)) + level = LOG_DEBUG; btu64_to_buf(&n_seqstt, t_buf, sizeof(t_buf)); bt_to_buf(&(cd->tv_sec), t_buf2, sizeof(t_buf2)); - LOGMSG(level, "SEQ dup %s %"PRIu64" set %d/%"PRIu64"=%s/%"PRIu64 - " %s/%s v%"PRIu64"/^%"PRIu64"/M%"PRIu64 - "/T%"PRIu64"/L%"PRIu64"/S%"PRIu64"/H%"PRIu64 - "/OK%"PRIu64" cmd=%.42s...", + LOGMSG(level, "SEQ dup%s %c:%c %s %"PRIu64" set %d/%"PRIu64 + "=%s/%"PRIu64" %s/%s v%"PRIu64"/^%"PRIu64 + "/M%"PRIu64"/T%"PRIu64"/L%"PRIu64"/S%"PRIu64 + "/H%"PRIu64"/OK%"PRIu64" cmd=%.42s...", + (level == LOG_DEBUG) ? "*" : EMPTY, + SICHR(seqitemflags), SICHR(seqitem_copy.flags), nam, n_seqcmd, set, n_seqstt, t_buf, n_seqpid, t_buf2, code, seqset_copy.seqdata[seq].minseq, @@ -2009,15 +2060,12 @@ setitemdata: } if (wastrans) { - int level = LOG_DEBUG; - if (warndup) - level = LOG_WARNING; btu64_to_buf(&n_seqstt, t_buf, sizeof(t_buf)); bt_to_buf(&(cd->tv_sec), t_buf2, sizeof(t_buf2)); - LOGMSG(level, "SEQ found trans %s %"PRIu64" set %d/%"PRIu64 - "=%s/%"PRIu64" %s/%s", - nam, n_seqcmd, set, n_seqstt, t_buf, n_seqpid, - t_buf2, code); + LOGWARNING("SEQ found trans %s %"PRIu64" set %d/%"PRIu64 + "=%s/%"PRIu64" %s/%s", + nam, n_seqcmd, set, n_seqstt, t_buf, n_seqpid, + t_buf2, code); } if (gotstale || gotstalestart || gothigh) { @@ -2080,13 +2128,13 @@ setitemdata: static enum cmd_values process_seq(K_ITEM *seqall, int which, tv_t *cd, tv_t *now, char *msg, K_TREE *trf_root, - bool wantauth) + bool wantauth, int seqitemflags) { uint64_t n_seqall, n_seqstt, n_seqpid, n_seqcmd; K_ITEM *seqstt, *seqpid, *seqcmd, *i_code; char *err = NULL, *st = NULL; size_t len, off; - bool dupall, dupcmd, warndup; + bool dupall, dupcmd; char *code = NULL; char buf[64]; @@ -2169,19 +2217,10 @@ static enum cmd_values process_seq(K_ITEM *seqall, int which, tv_t *cd, code = EMPTY; } - if (!startup_complete) - warndup = true; - else { - if (reload_queue_complete) - warndup = true; - else - warndup = false; - } - dupall = update_seq(SEQ_ALL, n_seqall, n_seqstt, n_seqpid, SEQALL, - now, cd, code, warndup, msg); + now, cd, code, seqitemflags, msg); dupcmd = update_seq(ckdb_cmds[which].seq, n_seqcmd, n_seqstt, n_seqpid, - buf, now, cd, code, warndup, msg); + buf, now, cd, code, seqitemflags, msg); if (dupall != dupcmd) { // Bad/corrupt data or a code bug @@ -2212,7 +2251,8 @@ static enum cmd_values process_seq(K_ITEM *seqall, int which, tv_t *cd, static enum cmd_values breakdown(K_TREE **trf_root, K_STORE **trf_store, char *buf, int *which_cmds, char *cmd, - char *id, tv_t *now, tv_t *cd, bool wantauth) + char *id, tv_t *now, tv_t *cd, bool wantauth, + int seqitemflags) { char reply[1024] = ""; TRANSFER *transfer; @@ -2530,7 +2570,7 @@ static enum cmd_values breakdown(K_TREE **trf_root, K_STORE **trf_store, if (seqall) { enum cmd_values ret; ret = process_seq(seqall, *which_cmds, cd, now, buf, - *trf_root, wantauth); + *trf_root, wantauth, seqitemflags); free(cmdptr); return ret; } else { @@ -3594,10 +3634,13 @@ static void *socketer(__maybe_unused void *arg) else LOGDEBUG("Duplicate '%s' message received", duptype); } else { + int seqitemflags = SI_SOCKET; + if (!reload_queue_complete) + seqitemflags = SI_EARLYSOCK; LOGQUE(buf); cmdnum = breakdown(&trf_root, &trf_store, buf, &which_cmds, cmd, id, &now, - &cd, true); + &cd, true, seqitemflags); switch (cmdnum) { case CMD_DUPSEQ: snprintf(reply, sizeof(reply), "%s.%ld.dup.", id, now.tv_sec); @@ -3902,7 +3945,7 @@ static void *socketer(__maybe_unused void *arg) return NULL; } -static bool reload_line(PGconn *conn, char *filename, uint64_t count, char *buf) +static void reload_line(PGconn *conn, char *filename, uint64_t count, char *buf) { char cmd[CMD_SIZ+1], id[ID_SIZ+1]; enum cmd_values cmdnum; @@ -3913,7 +3956,7 @@ static bool reload_line(PGconn *conn, char *filename, uint64_t count, char *buf) TRANSFER *transfer; K_ITEM *item; tv_t now, cd; - bool finished; + bool matched; // Once we've read the message setnow(&now); @@ -3929,19 +3972,19 @@ static bool reload_line(PGconn *conn, char *filename, uint64_t count, char *buf) else LOGERR("%s() Empty message line %"PRIu64, __func__, count); } else { - finished = false; + matched = false; ck_wlock(&fpm_lock); - if (first_pool_message && strcmp(first_pool_message, buf) == 0) - finished = true; + if (first_pool_message && strcmp(first_pool_message, buf) == 0) { + matched = true; + FREENULL(first_pool_message); + } ck_wunlock(&fpm_lock); - if (finished) { + if (matched) LOGERR("%s() reload ckpool queue match at line %"PRIu64, __func__, count); - return true; - } LOGQUE(buf); cmdnum = breakdown(&trf_root, &trf_store, buf, &which_cmds, - cmd, id, &now, &cd, false); + cmd, id, &now, &cd, false, SI_RELOAD); switch (cmdnum) { // Don't ever attempt to double process reload data case CMD_DUPSEQ: @@ -4029,8 +4072,6 @@ static bool reload_line(PGconn *conn, char *filename, uint64_t count, char *buf) } tick(); - - return false; } // 10Mb for now - transactiontree can be large @@ -4112,10 +4153,10 @@ static bool reload_from(tv_t *start) PGconn *conn = NULL; char buf[DATE_BUFSIZ+1], run[DATE_BUFSIZ+1]; size_t rflen = strlen(restorefrom); - char *missingfirst = NULL, *missinglast = NULL; + char *missingfirst = NULL, *missinglast = NULL, *st = NULL; int missing_count; int processing; - bool finished = false, matched = false, ret = true, ok, apipe = false; + bool finished = false, ret = true, ok, apipe = false; char *filename = NULL; uint64_t count, total; tv_t now, begin; @@ -4160,8 +4201,9 @@ static bool reload_from(tv_t *start) * aborting early and not get the few slightly later out of * order messages in the log file */ while (!everyone_die && - logline(reload_buf, MAX_READ, fp, filename)) - matched = reload_line(conn, filename, ++count, reload_buf); + logline(reload_buf, MAX_READ, fp, filename)) { + reload_line(conn, filename, ++count, reload_buf); + } LOGWARNING("%s(): %sread %"PRIu64" line%s from %s", __func__, @@ -4244,16 +4286,14 @@ static bool reload_from(tv_t *start) if (everyone_die) return true; - if (!matched) { - ck_wlock(&fpm_lock); - if (first_pool_message) { - LOGERR("%s() reload completed without finding ckpool queue match '%.32s'...", - __func__, first_pool_message); - LOGERR("%s() restart ckdb to resolve this", __func__); - ret = false; - } - ck_wunlock(&fpm_lock); + ck_wlock(&fpm_lock); + if (first_pool_message) { + LOGERR("%s() reload didn't find the first ckpool queue '%.32s...", + __func__, st = safe_text(first_pool_message)); + FREENULL(st); + FREENULL(first_pool_message); } + ck_wunlock(&fpm_lock); reloading = false; FREENULL(reload_buf); diff --git a/src/ckdb.h b/src/ckdb.h index f7b8f43a..71b985b5 100644 --- a/src/ckdb.h +++ b/src/ckdb.h @@ -55,7 +55,7 @@ #define DB_VLOCK "1" #define DB_VERSION "1.0.0" -#define CKDB_VERSION DB_VERSION"-1.075" +#define CKDB_VERSION DB_VERSION"-1.078" #define WHERE_FFL " - from %s %s() line %d" #define WHERE_FFL_HERE __FILE__, __func__, __LINE__ @@ -860,7 +860,23 @@ enum seq_num { // Ensure size is a (multiple of 8)-1 #define SEQ_CODE 15 +#define SICHR(_sif) (((_sif) == SI_EARLYSOCK) ? 'E' : \ + (((_sif) == SI_RELOAD) ? 'R' : \ + (((_sif) == SI_SOCKET) ? 'S' : '?'))) + +// Msg from the socket before startup completed - ignore if it was a DUP +#define SI_EARLYSOCK 1 +// Msg was from reload +#define SI_RELOAD 2 +// Msg from the socket after startup completed +#define SI_SOCKET 4 + +/* An SI_EARLYSOCK item vs an SI_RELOAD item is not considered a DUP + * since the reload reads to the end of the reload file after + * the match between the queue and the reload has been found */ + typedef struct seqitem { + int flags; tv_t cd; // sec:0=missing, usec:0=miss !0=trans tv_t time; char code[SEQ_CODE+1]; diff --git a/src/klist.c b/src/klist.c index 65e30448..1254628d 100644 --- a/src/klist.c +++ b/src/klist.c @@ -9,11 +9,36 @@ #include "klist.h" +#define _CHKLIST(_list, _name) do {\ + if (!_list) { \ + quithere(1, "%s() can't process a NULL " _name \ + KLIST_FFL, \ + __func__, KLIST_FFL_PASS); \ + } \ + } while (0); + +#define CHKLIST(__list) _CHKLIST(__list, "list") + +#define CHKLS(__list) _CHKLIST(__list, "list/store") + +#define _CHKITEM(_item, _list, _name) do {\ + if (!_item) { \ + quithere(1, "%s() can't process a NULL %s " _name \ + KLIST_FFL, \ + __func__, _list->name, \ + KLIST_FFL_PASS); \ + } \ + } while (0); + +#define CHKITEM(__item, __list) _CHKITEM(__item, __list, "item") + static void k_alloc_items(K_LIST *list, KLIST_FFL_ARGS) { K_ITEM *item; int allocate, i; + CHKLIST(list); + if (list->is_store) { quithere(1, "List %s store can't %s()" KLIST_FFL, list->name, __func__, KLIST_FFL_PASS); @@ -76,10 +101,12 @@ static void k_alloc_items(K_LIST *list, KLIST_FFL_ARGS) } } -K_STORE *k_new_store(K_LIST *list) +K_STORE *_k_new_store(K_LIST *list, KLIST_FFL_ARGS) { K_STORE *store; + CHKLIST(list); + store = calloc(1, sizeof(*store)); if (!store) quithere(1, "Failed to calloc store for %s", list->name); @@ -136,6 +163,8 @@ K_ITEM *_k_unlink_head(K_LIST *list, KLIST_FFL_ARGS) { K_ITEM *item; + CHKLS(list); + if (!(list->head) && !(list->is_store)) k_alloc_items(list, KLIST_FFL_PASS); @@ -163,6 +192,8 @@ K_ITEM *_k_unlink_head_zero(K_LIST *list, KLIST_FFL_ARGS) { K_ITEM *item; + CHKLS(list); + item = _k_unlink_head(list, KLIST_FFL_PASS); if (item) @@ -176,6 +207,8 @@ K_ITEM *_k_unlink_tail(K_LIST *list, KLIST_FFL_ARGS) { K_ITEM *item; + CHKLS(list); + if (!(list->do_tail)) { quithere(1, "List %s can't %s() - do_tail is false" KLIST_FFL, list->name, __func__, KLIST_FFL_PASS); @@ -200,6 +233,10 @@ K_ITEM *_k_unlink_tail(K_LIST *list, KLIST_FFL_ARGS) void _k_add_head(K_LIST *list, K_ITEM *item, KLIST_FFL_ARGS) { + CHKLS(list); + + CHKITEM(item, list); + if (item->name != list->name) { quithere(1, "List %s can't %s() a %s item" KLIST_FFL, list->name, __func__, item->name, KLIST_FFL_PASS); @@ -229,6 +266,10 @@ void _k_add_head(K_LIST *list, K_ITEM *item, KLIST_FFL_ARGS) /* slows it down (of course) - only for debugging void _k_free_head(K_LIST *list, K_ITEM *item, KLIST_FFL_ARGS) { + CHKLS(list); + + CHKITEM(item, list); + memset(item->data, 0xff, list->siz); _k_add_head(list, item, KLIST_FFL_PASS); } @@ -236,6 +277,10 @@ void _k_free_head(K_LIST *list, K_ITEM *item, KLIST_FFL_ARGS) void _k_add_tail(K_LIST *list, K_ITEM *item, KLIST_FFL_ARGS) { + CHKLS(list); + + CHKITEM(item, list); + if (item->name != list->name) { quithere(1, "List %s can't %s() a %s item" KLIST_FFL, list->name, __func__, item->name, KLIST_FFL_PASS); @@ -265,8 +310,51 @@ void _k_add_tail(K_LIST *list, K_ITEM *item, KLIST_FFL_ARGS) list->count_up++; } +// Insert item into the list next after 'after' +void _k_insert_after(K_LIST *list, K_ITEM *item, K_ITEM *after, KLIST_FFL_ARGS) +{ + CHKLS(list); + + CHKITEM(item, list); + + _CHKITEM(item, after, "after"); + + if (item->name != list->name) { + quithere(1, "List %s can't %s() a %s item" KLIST_FFL, + list->name, __func__, item->name, KLIST_FFL_PASS); + } + + if (after->name != list->name) { + quithere(1, "List %s can't %s() a %s after" KLIST_FFL, + list->name, __func__, item->name, KLIST_FFL_PASS); + } + + if (item->prev || item->next) { + quithere(1, "%s() added item %s still linked" KLIST_FFL, + __func__, item->name, KLIST_FFL_PASS); + } + + item->prev = after; + item->next = after->next; + if (item->next) + item->next->prev = item; + after->next = item; + + if (list->do_tail) { + if (list->tail == after) + list->tail = item; + } + + list->count++; + list->count_up++; +} + void _k_unlink_item(K_LIST *list, K_ITEM *item, KLIST_FFL_ARGS) { + CHKLS(list); + + CHKITEM(item, list); + if (item->name != list->name) { quithere(1, "List %s can't %s() a %s item" KLIST_FFL, list->name, __func__, item->name, KLIST_FFL_PASS); @@ -293,6 +381,10 @@ void _k_unlink_item(K_LIST *list, K_ITEM *item, KLIST_FFL_ARGS) void _k_list_transfer_to_head(K_LIST *from, K_LIST *to, KLIST_FFL_ARGS) { + _CHKLIST(from, "from list/store"); + + _CHKLIST(to, "to list/store"); + if (from->name != to->name) { quithere(1, "List %s can't %s() to a %s list" KLIST_FFL, from->name, __func__, to->name, KLIST_FFL_PASS); @@ -323,6 +415,10 @@ void _k_list_transfer_to_head(K_LIST *from, K_LIST *to, KLIST_FFL_ARGS) void _k_list_transfer_to_tail(K_LIST *from, K_LIST *to, KLIST_FFL_ARGS) { + _CHKLIST(from, "from list/store"); + + _CHKLIST(to, "to list/store"); + if (from->name != to->name) { quithere(1, "List %s can't %s() to a %s list" KLIST_FFL, from->name, __func__, to->name, KLIST_FFL_PASS); @@ -355,6 +451,8 @@ K_LIST *_k_free_list(K_LIST *list, KLIST_FFL_ARGS) { int i; + CHKLIST(list); + if (list->is_store) { quithere(1, "List %s can't %s() a store" KLIST_FFL, list->name, __func__, KLIST_FFL_PASS); @@ -379,6 +477,8 @@ K_LIST *_k_free_list(K_LIST *list, KLIST_FFL_ARGS) K_STORE *_k_free_store(K_STORE *store, KLIST_FFL_ARGS) { + _CHKLIST(store, "store"); + if (!(store->is_store)) { quithere(1, "Store %s can't %s() the list" KLIST_FFL, store->name, __func__, KLIST_FFL_PASS); @@ -394,6 +494,8 @@ void _k_cull_list(K_LIST *list, KLIST_FFL_ARGS) { int i; + CHKLIST(list); + if (list->is_store) { quithere(1, "List %s can't %s() a store" KLIST_FFL, list->name, __func__, KLIST_FFL_PASS); diff --git a/src/klist.h b/src/klist.h index 26aaf303..602bdac1 100644 --- a/src/klist.h +++ b/src/klist.h @@ -71,7 +71,8 @@ typedef struct k_list { // Upgrade I to W #define K_ULOCK(_list) ck_ulock(_list->lock) -extern K_STORE *k_new_store(K_LIST *list); +extern K_STORE *_k_new_store(K_LIST *list, KLIST_FFL_ARGS); +#define k_new_store(_list) _k_new_store(_list, KLIST_FFL_HERE) extern K_LIST *_k_new_list(const char *name, size_t siz, int allocate, int limit, bool do_tail, KLIST_FFL_ARGS); #define k_new_list(_name, _siz, _allocate, _limit, _do_tail) _k_new_list(_name, _siz, _allocate, _limit, _do_tail, KLIST_FFL_HERE) extern K_ITEM *_k_unlink_head(K_LIST *list, KLIST_FFL_ARGS); @@ -86,11 +87,13 @@ extern void _k_add_head(K_LIST *list, K_ITEM *item, KLIST_FFL_ARGS); #define k_free_head(__list, __item) _k_add_head(__list, __item, KLIST_FFL_HERE) extern void _k_add_tail(K_LIST *list, K_ITEM *item, KLIST_FFL_ARGS); #define k_add_tail(_list, _item) _k_add_tail(_list, _item, KLIST_FFL_HERE) +extern void _k_insert_after(K_LIST *list, K_ITEM *item, K_ITEM *after, KLIST_FFL_ARGS); +#define k_insert_after(_list, _item, _after) _k_insert_after(_list, _item, _after, KLIST_FFL_HERE) extern void _k_unlink_item(K_LIST *list, K_ITEM *item, KLIST_FFL_ARGS); #define k_unlink_item(_list, _item) _k_unlink_item(_list, _item, KLIST_FFL_HERE) -void _k_list_transfer_to_head(K_LIST *from, K_LIST *to, KLIST_FFL_ARGS); +extern void _k_list_transfer_to_head(K_LIST *from, K_LIST *to, KLIST_FFL_ARGS); #define k_list_transfer_to_head(_from, _to) _k_list_transfer_to_head(_from, _to, KLIST_FFL_HERE) -void _k_list_transfer_to_tail(K_LIST *from, K_LIST *to, KLIST_FFL_ARGS); +extern void _k_list_transfer_to_tail(K_LIST *from, K_LIST *to, KLIST_FFL_ARGS); #define k_list_transfer_to_tail(_from, _to) _k_list_transfer_to_tail(_from, _to, KLIST_FFL_HERE) extern K_LIST *_k_free_list(K_LIST *list, KLIST_FFL_ARGS); #define k_free_list(_list) _k_free_list(_list, KLIST_FFL_HERE)