diff --git a/mx_util.c b/mx_util.c index 809e1c63..0dd65101 100644 --- a/mx_util.c +++ b/mx_util.c @@ -1304,3 +1304,30 @@ int mx_daemon(int nochdir, int noclose) { return daemon(nochdir, noclose); } + +void _mx_sort_linked_list (void **list, int (*cmp)(void *o1,void *o2), void ** getnextptr(void *o)) { + + void *unsorted=*list; + void *sorted=NULL; + + while (unsorted) { + void *o; + void **s_ptr; + void *s; + + o=unsorted; + unsorted=*(getnextptr(o)); + + s_ptr=&sorted; + while(1) { + s=*s_ptr; + if (s==NULL || cmp(o,s)<0) { + break; + } + s_ptr=getnextptr(s); + } + *(getnextptr(o))=s; + *s_ptr=o; + } + *list=sorted; +} diff --git a/mx_util.h b/mx_util.h index f20fe38b..46107414 100644 --- a/mx_util.h +++ b/mx_util.h @@ -166,4 +166,8 @@ int mx_mkdir_p(char *path, mode_t mode); int mx_daemon(int nochdir, int noclose); +void _mx_sort_linked_list(void **list, int (*cmp)(void *o1,void *o2), void ** (*getnextptr)(void *o)); +#define mx_sort_linked_list(list,cmp,getnextptr) _mx_sort_linked_list((void **)(list),(int (*)(void *,void *))(cmp),(void ** (*)(void *))(getnextptr)) + + #endif diff --git a/mxq_log.c b/mxq_log.c index 77b40b5f..b55f209c 100644 --- a/mxq_log.c +++ b/mxq_log.c @@ -46,45 +46,18 @@ int mx_log_print(char *msg, size_t len) { char timebuf[1024]; - static char *lastmsg = NULL; - static size_t lastlen = 0; - static int cnt = 0; - if (!msg) { - mx_free_null(lastmsg); return 0; } if (!len) return 0; - if (!*msg) - return -(errno=EINVAL); - - if (lastmsg && lastlen == len) { - if (mx_streq(msg, lastmsg)) { - cnt++; - mx_free_null(msg); - return 2; - } - } - timetag(timebuf, sizeof(timebuf)); - if (cnt > 1) - fprintf(stderr, "%s %s[%d]: last message repeated %d times\n", timebuf, program_invocation_short_name, getpid(), cnt); - else if (cnt == 1) - fprintf(stderr, "%s %s[%d]: %s\n", timebuf, program_invocation_short_name, getpid(), lastmsg); - - if (lastmsg) - mx_free_null(lastmsg); - - lastmsg = msg; - lastlen = len; - cnt = 0; - fprintf(stderr, "%s %s[%d]: %s\n", timebuf, program_invocation_short_name, getpid(), msg); fflush(stderr); + mx_free_null(msg); return 1; } diff --git a/mxqd.c b/mxqd.c index 1a723c7b..7d188810 100644 --- a/mxqd.c +++ b/mxqd.c @@ -53,11 +53,12 @@ #define RUNNING_AS_ROOT (getuid() == 0) -volatile sig_atomic_t global_sigint_cnt=0; -volatile sig_atomic_t global_sigterm_cnt=0; -volatile sig_atomic_t global_sigquit_cnt=0; -volatile sig_atomic_t global_sigrestart_cnt=0; +static int global_sigint_cnt=0; +static int global_sigterm_cnt=0; +static int global_sigquit_cnt=0; +static int global_sigrestart_cnt=0; +static sigset_t all_signals; int mxq_redirect_output(char *stdout_fname, char *stderr_fname); void server_free(struct mxq_server *server); @@ -700,21 +701,6 @@ int server_init(struct mxq_server *server, int argc, char *argv[]) return 0; } -static void reset_signals() -{ - signal(SIGINT, SIG_DFL); - signal(SIGTERM, SIG_DFL); - signal(SIGQUIT, SIG_DFL); - signal(SIGHUP, SIG_DFL); - signal(SIGTSTP, SIG_DFL); - signal(SIGTTIN, SIG_DFL); - signal(SIGTTOU, SIG_DFL); - signal(SIGCHLD, SIG_DFL); - signal(SIGPIPE, SIG_DFL); - signal(SIGUSR1, SIG_DFL); - signal(SIGUSR2, SIG_DFL); -} - static int init_child_process(struct mxq_group_list *glist, struct mxq_job *job) { struct mxq_server *server; @@ -732,7 +718,7 @@ static int init_child_process(struct mxq_group_list *glist, struct mxq_job *job) server = glist->user->server; group = &glist->group; - reset_signals(); + sigprocmask(SIG_UNBLOCK,&all_signals,NULL); passwd = getpwuid(group->user_uid); if (!passwd) { @@ -1056,14 +1042,6 @@ int reaper_process(struct mxq_server *server,struct mxq_group_list *glist, struc group = &glist->group; - reset_signals(); - - signal(SIGINT, SIG_IGN); - signal(SIGTERM, SIG_IGN); - signal(SIGQUIT, SIG_IGN); - signal(SIGHUP, SIG_IGN); - signal(SIGXCPU, SIG_IGN); - res = setsid(); if (res < 0) { mx_log_warning("reaper_process setsid: %m"); @@ -1081,7 +1059,7 @@ int reaper_process(struct mxq_server *server,struct mxq_group_list *glist, struc mx_log_err("fork: %m"); return pid; } else if (pid == 0) { - mx_log_info("starting user process."); + mx_log_debug("starting user process."); res = user_process(glist, job); _exit(EX__MAX+1); } @@ -1205,14 +1183,14 @@ unsigned long start_job(struct mxq_group_list *glist) job->host_pid, getpgrp()); - mx_log_info("starting reaper process."); + mx_log_debug("starting reaper process."); mx_mysql_finish(&server->mysql); res = reaper_process(server, glist, job); mxq_job_free_content(job); - mx_log_info("shutting down reaper, bye bye."); + mx_log_debug("shutting down reaper, bye bye."); mx_log_finish(); server_free(server); _exit(res<0 ? EX__MAX+1 : 0); @@ -1386,9 +1364,6 @@ void server_dump(struct mxq_server *server) struct mxq_group *group; struct mxq_job *job; - if (!server->user_cnt) - return; - mx_log_info("====================== SERVER DUMP START ======================"); for (ulist = server->users; ulist; ulist = ulist->next) { if (!ulist->groups) { @@ -2239,6 +2214,7 @@ int load_running_groups(struct mxq_server *server) free(grps); server_remove_orphaned_groups(server); + server_sort_groups_by_priority(server); return total; } @@ -2302,29 +2278,69 @@ int recover_from_previous_crash(struct mxq_server *server) return res; } -/**********************************************************************/ -static void no_handler(int sig) {} - -static void sig_handler(int sig) +static void process_signal(struct mxq_server *server,int sig,int extra) { - if (sig == SIGINT) { - global_sigint_cnt++; - return; - } - - if (sig == SIGTERM) { - global_sigterm_cnt++; - return; + switch (sig) { + case SIGINT: + mx_log_info("received sigint"); + global_sigint_cnt++; + break; + case SIGTERM: + mx_log_info("received sigterm"); + global_sigterm_cnt++; + break; + case SIGQUIT: + mx_log_info("received sigquit"); + global_sigquit_cnt++; + break; + case SIGUSR1: + mx_log_info("received sigusr2"); + global_sigrestart_cnt++; + break; + case SIGUSR2: + switch (extra) { + case 10: + mx_log_info("received sigusr2 extra %d (dump)",extra); + server_dump(server); + break; + case 20: + mx_log_info("received sigusr2 extra %d (set loglevel info)",extra); + mx_log_level_set(MX_LOG_INFO); + break; + case 21: + mx_log_info("received sigusr2 extra %d (set loglevel debug)",extra); + mx_log_level_set(MX_LOG_DEBUG); + break; + default: + mx_log_warning("received sigusr2 extra %d (unexpected!)",extra); + break; + } + break; + case SIGCHLD: + mx_log_info("received sigchld"); + break; + default: + mx_log_warning("received signal %d (unexpected!)",sig); + break; } +} - if (sig == SIGQUIT) { - global_sigquit_cnt++; - return; - } +static void update_status(struct mxq_server *server) +{ + struct mxq_daemon *daemon = &server->daemon; - if (sig == SIGUSR1) { - global_sigrestart_cnt++; - return; + if (!server->slots_running) { + mxq_daemon_set_status(server->mysql, daemon, MXQ_DAEMON_STATUS_IDLE); + } else { + if (server->slots_running < server->slots) + mxq_daemon_set_status(server->mysql, daemon, MXQ_DAEMON_STATUS_RUNNING); + else if (server->slots_running > server->slots) + mxq_daemon_set_status(server->mysql, daemon, MXQ_DAEMON_STATUS_BACKFILL); + else + if (server->threads_running == server->slots) + mxq_daemon_set_status(server->mysql, daemon, MXQ_DAEMON_STATUS_CPUOPTIMAL); + else + mxq_daemon_set_status(server->mysql, daemon, MXQ_DAEMON_STATUS_FULL); } } @@ -2339,13 +2355,26 @@ int main(int argc, char *argv[]) unsigned long slots_started = 0; unsigned long slots_returned = 0; + static sigset_t sigset; + int res; int fail = 0; + static struct timespec poll_interval={10,0}; /* 10 seconds */ + siginfo_t siginfo; int saved_argc; _mx_cleanup_free_ char *saved_argv_str = NULL; _mx_cleanup_free_ char *saved_cwd = NULL; + sigfillset(&all_signals); + + sigemptyset(&sigset); + sigaddset(&sigset,SIGINT); + sigaddset(&sigset,SIGTERM); + sigaddset(&sigset,SIGQUIT); + sigaddset(&sigset,SIGUSR1); + sigaddset(&sigset,SIGUSR2); + sigaddset(&sigset,SIGCHLD); /*** server init ***/ @@ -2390,14 +2419,7 @@ int main(int argc, char *argv[]) /*** main loop ***/ - signal(SIGINT, sig_handler); - signal(SIGTERM, sig_handler); - signal(SIGQUIT, sig_handler); - signal(SIGUSR1, sig_handler); - signal(SIGTSTP, SIG_IGN); - signal(SIGTTIN, SIG_IGN); - signal(SIGTTOU, SIG_IGN); - signal(SIGCHLD, no_handler); + sigprocmask(SIG_BLOCK,&all_signals,NULL); res = recover_from_previous_crash(server); if (res < 0) { @@ -2408,20 +2430,20 @@ int main(int argc, char *argv[]) if (server->recoveronly) fail = 1; - server_dump(server); - + update_status(server); + mx_log_info("entering main loop"); while (!global_sigint_cnt && !global_sigterm_cnt && !global_sigquit_cnt && !global_sigrestart_cnt && !fail) { + mx_log_debug("main loop - wait for signals max %ld sec",poll_interval.tv_sec); + res=sigtimedwait(&sigset,&siginfo,&poll_interval); + if (res>0) + process_signal(server,res,siginfo.si_int); + slots_returned = catchall(server); slots_returned += fspool_scan(server); if (slots_returned) mx_log_info("slots_returned=%lu :: Main Loop freed %lu slots.", slots_returned, slots_returned); - if (slots_started || slots_returned) { - server_dump(server); - slots_started = 0; - } - group_cnt = load_running_groups(server); if (group_cnt) mx_log_debug("group_cnt=%d :: %d Groups loaded", group_cnt, group_cnt); @@ -2430,51 +2452,23 @@ int main(int argc, char *argv[]) killall_over_time(server); killall_over_memory(server); - if (!server->group_cnt) { - assert(!server->jobs_running); - assert(!group_cnt); - mxq_daemon_set_status(server->mysql, daemon, MXQ_DAEMON_STATUS_IDLE); - mx_log_info("Nothing to do. Sleeping for a short while. (1 second)"); - sleep(1); - continue; - } - - if (server->slots_running >= server->slots) { - if (server->threads_running == server->slots) { - mxq_daemon_set_status(server->mysql, daemon, MXQ_DAEMON_STATUS_CPUOPTIMAL); - } else if (server->slots_running > server->slots) { - mxq_daemon_set_status(server->mysql, daemon, MXQ_DAEMON_STATUS_BACKFILL); - } else { - mxq_daemon_set_status(server->mysql, daemon, MXQ_DAEMON_STATUS_FULL); - } - mx_log_info("All slots running. Sleeping for a short while (7 seconds)."); - sleep(7); - continue; - } - - slots_started = start_user_with_least_running_global_slot_count(server); - if (slots_started == -1) { - mxq_daemon_set_status(server->mysql, daemon, MXQ_DAEMON_STATUS_WAITING); - mx_log_info("no slots_started => we have users waiting for free slots. Sleeping (3 seconds)."); - sleep(3); - slots_started = 0; - continue; - } else if (slots_started) { - mx_log_info("slots_started=%lu :: Main Loop started %lu slots.", slots_started, slots_started); - } - - if (!slots_started && !slots_returned && !global_sigint_cnt && !global_sigterm_cnt) { - if (!server->jobs_running) { - mxq_daemon_set_status(server->mysql, daemon, MXQ_DAEMON_STATUS_IDLE); - mx_log_info("Tried Hard and nobody is doing anything. Sleeping for a long while (15 seconds)."); - sleep(15); - } else { - mxq_daemon_set_status(server->mysql, daemon, MXQ_DAEMON_STATUS_RUNNING); - mx_log_info("Tried Hard. But have done nothing. Sleeping for a very short while (3 seconds)."); - sleep(3); + if (server->slots_runningslots && server->group_cnt) { + slots_started=0; + do { + res = start_user_with_least_running_global_slot_count(server); + if (res>0) { + slots_started+=res; + } + } while (res>0); + if (slots_started) + mx_log_info("Main loop started %lu slots.", slots_started); + if (res<0) { + mx_log_info("No more slots started because we have users waiting for free slots"); + mxq_daemon_set_status(server->mysql, daemon, MXQ_DAEMON_STATUS_WAITING); + continue; } - continue; } + update_status(server); } /*** clean up ***/ @@ -2514,7 +2508,10 @@ int main(int argc, char *argv[]) server->jobs_running, global_sigint_cnt, global_sigterm_cnt); - sleep(1); + mx_log_debug("termination loop - wait for signals max %ld sec",poll_interval.tv_sec); + res=sigtimedwait(&sigset,&siginfo,&poll_interval); + if (res>0) + process_signal(server,res,siginfo.si_int); } mxq_daemon_shutdown(server->mysql, daemon); @@ -2556,6 +2553,5 @@ int main(int argc, char *argv[]) mx_log_info("cu, mx."); mx_log_finish(); - - exit(0); + return(0); } diff --git a/mxqd_control.c b/mxqd_control.c index 419d6bfb..36814e26 100644 --- a/mxqd_control.c +++ b/mxqd_control.c @@ -519,52 +519,42 @@ int server_remove_orphaned_groups(struct mxq_server *server) return cnt; } -void server_sort_users_by_running_global_slot_count(struct mxq_server *server) +static int cmp_users_by_global_running_slots(struct mxq_user_list *u1,struct mxq_user_list *u2) { - struct mxq_user_list *ulist; - struct mxq_user_list *unext; - struct mxq_user_list *uprev; - struct mxq_user_list *uroot; - struct mxq_user_list *current; - - assert(server); - - if (!server->user_cnt) - return; + return + u1->global_slots_running > u2->global_slots_running ? 1 + : u1->global_slots_running < u2->global_slots_running ? -1 + : 0; +} - for (ulist = server->users, uroot = NULL; ulist; ulist = unext) { - unext = ulist->next; +static struct mxq_user_list **get_users_nextptr(struct mxq_user_list *u) +{ + return &u->next; +} - ulist->next = NULL; +void server_sort_users_by_running_global_slot_count(struct mxq_server *server) +{ + assert(server); + mx_sort_linked_list(&server->users,cmp_users_by_global_running_slots,get_users_nextptr); +} - if (!uroot) { - uroot = ulist; - continue; - } +static int cmp_groups_by_priority(struct mxq_group_list *g1,struct mxq_group_list *g2) +{ + return + g1->group.group_priority > g2->group.group_priority ? 1 + : g1->group.group_priority < g2->group.group_priority ? -1 + : 0; +} - for (current = uroot, uprev = NULL; (current || uprev); uprev = current, current = current->next) { - if (!current) { - uprev->next = ulist; - break; - } - if (ulist->global_slots_running > current->global_slots_running) { - continue; - } - if (ulist->global_slots_running == current->global_slots_running - && ulist->global_threads_running > current->global_threads_running) { - continue; - } +static struct mxq_group_list **get_groups_nextptr(struct mxq_group_list *g) { + return &g->next; +} - ulist->next = current; +void server_sort_groups_by_priority(struct mxq_server *server) { + assert(server); + struct mxq_user_list *user; - if (!uprev) { - uroot = ulist; - } else { - uprev->next = ulist; - } - break; - } + for (user=server->users;user;user=user->next) { + mx_sort_linked_list(&user->groups,cmp_groups_by_priority,get_groups_nextptr); } - - server->users = uroot; } diff --git a/mxqd_control.h b/mxqd_control.h index 3ea69a5a..a0032ccd 100644 --- a/mxqd_control.h +++ b/mxqd_control.h @@ -20,7 +20,7 @@ struct mxq_job_list *group_list_add_job(struct mxq_group_list *glist, struct mxq int server_remove_orphaned_groups(struct mxq_server *server); void job_list_remove_self(struct mxq_job_list *jlist); - +void server_sort_groups_by_priority(struct mxq_server *server); #endif diff --git a/mxqdctl-hostconfig.sh b/mxqdctl-hostconfig.sh index 8b8f75f0..1e1de83f 100755 --- a/mxqdctl-hostconfig.sh +++ b/mxqdctl-hostconfig.sh @@ -112,16 +112,49 @@ case "${BASH_ARGV[0]}" in reload|restart) reload_all_started ;; + stopall) - killall -u "${USER}" "${mxqd}" + env kill mxqd + ;; + killall) + env kill -int mxqd + ;; + quitall) + env kill -quit mxqd + ;; + reloadall|restartall) + env kill -usr1 mxqd ;; + stateall) + env kill -usr2 -q 10 mxqd + ;; + setinfoall|setnodebugall) + env kill -usr2 -q 20 mxqd + ;; + setdebugall) + env kill -usr2 -q 21 mxqd + ;; + *) echo "usage $0 CMD" + echo "" + echo "to mxqd configured by hostconfig:" + echo "" echo " start : start mxqd (if configured by hostconfig)" echo " stop : tell mxqd to stop accepting new jobs, wait for running jobs, exit" echo " kill : tell mxqd to stop accepting new jobs, kill and wait for running jobs, exit" echo " quit : tell mxqd to exit (leave jobs running)" echo " reload|restart : tell mxqd to re-exec itself, leave jobs running" - echo " stopall : as 'stop', but to any mxqd owned by calling user" + echo "" + echo "to all mxqd owned by calling user:" + echo "" + echo " stopall : tell mxqd to stop accepting new jobs, wait for running jobs, exit" + echo " killall : tell mxqd to stop accepting new jobs, kill and wait for running jobs, exit" + echo " quitall : tell mxqd to exit (leave jobs running)" + echo " reloadall|restartall : tell mxqd to re-exec itself, leave jobs running" + echo "" + echo " stateall : tell mxqd to dump state" + echo " setdebugall : tell to set loglevel to debug" + echo " setinfoall|setnodebugall : tell mxqd to set loglevel to info" ;; esac diff --git a/test_mx_util.c b/test_mx_util.c index a440ebf7..05389f42 100644 --- a/test_mx_util.c +++ b/test_mx_util.c @@ -14,13 +14,13 @@ static void test_mx_strskipwhitespaces(void) { char *s; - assert(s = mx_strskipwhitespaces(" abc ")); + assert((s = mx_strskipwhitespaces(" abc "))); assert(strcmp(s, "abc ") == 0); - assert(s = mx_strskipwhitespaces("abc ")); + assert((s = mx_strskipwhitespaces("abc "))); assert(strcmp(s, "abc ") == 0); - assert(s = mx_strskipwhitespaces("")); + assert((s = mx_strskipwhitespaces(""))); assert(strcmp(s, "") == 0); } @@ -308,7 +308,7 @@ static void test_mx_strscan(void) _mx_cleanup_free_ char *line = NULL; _mx_cleanup_free_ struct mx_proc_pid_stat *pps = NULL; - assert(s = strdup("123 456 -789 246 abc")); + assert((s = strdup("123 456 -789 246 abc"))); str = s; assert(mx_strscan_ull(&str, &ull) == 0); @@ -333,7 +333,7 @@ static void test_mx_strscan(void) assert(mx_streq(s, "123 456 -789 246 abc")); mx_free_null(s); - assert(s = strdup("123")); + assert((s = strdup("123"))); str = s; assert(mx_strscan_ull(&str, &ull) == 0); assert(ull == 123); @@ -381,28 +381,28 @@ static void test_mx_strvec() { mx_strvec_push_str(&strvec,strdup("Bla")); mx_strvec_push_str(&strvec,strdup("lall")); - assert(str=mx_strvec_join("XXX",strvec)); + assert((str=mx_strvec_join("XXX",strvec))); assert(strcmp(str,"HalloXXXBlaXXXlall")==0); free(str); - assert(str=mx_strvec_join("",strvec)); + assert((str=mx_strvec_join("",strvec))); assert(strcmp(str,"HalloBlalall")==0); free(str); mx_strvec_free(strvec); strvec=mx_strvec_new(); - assert(str=mx_strvec_join("XXX",strvec)); + assert((str=mx_strvec_join("XXX",strvec))); assert(strcmp(str,"")==0); free(str); mx_strvec_push_str(&strvec,strdup("A")); - assert(str=mx_strvec_join("x",strvec)); + assert((str=mx_strvec_join("x",strvec))); assert(strcmp(str,"A")==0); free(str); mx_strvec_push_str(&strvec,strdup("")); - assert(str=mx_strvec_join("x",strvec)); + assert((str=mx_strvec_join("x",strvec))); assert(strcmp(str,"Ax")==0); free(str); mx_strvec_push_str(&strvec,strdup("B")); - assert(str=mx_strvec_join("x",strvec)); + assert((str=mx_strvec_join("x",strvec))); assert(strcmp(str,"AxxB")==0); free(str); mx_strvec_free(strvec); @@ -412,18 +412,18 @@ static void test_mx_strcat() { char *str; char *str2; - assert(str = mx_strconcat(NULL)); + assert((str = mx_strconcat(NULL))); assert(mx_streq(str, "")); mx_free_null(str); - assert(str = mx_strconcat("abc", "123")); + assert((str = mx_strconcat("abc", "123"))); assert(mx_streq(str, "abc123")); - assert(str2 = mx_strconcat(str, "def", str, "456")); + assert((str2 = mx_strconcat(str, "def", str, "456"))); assert(mx_streq(str2, "abc123defabc123456")); mx_free_null(str2); - assert(str2 = mx_strconcat(str)); + assert((str2 = mx_strconcat(str))); assert(mx_streq(str, str2)); mx_free_null(str); mx_free_null(str2); @@ -450,6 +450,86 @@ static void test_mx_cpuset(void) assert(mx_str_to_cpuset(&cpuset,"4-")<0); } +struct obj { + int i; + struct obj *next; +}; + +int obj_compare(struct obj *o1,struct obj *o2) +{ + return + o1->i > o2->i ? 1 + : o1->i < o2->i ? -1 + : 0; +} + +struct obj **obj_getnextptr(struct obj *o) { + return &o->next; +} + +static void test_listsort(void) +{ + struct obj o[10]; + struct obj *list; + + for (int i=0;i<10;i++) { + o[i].i=i; + o[i].next= i==9 ? NULL : &o[i+1]; + } + + /* () -> () */ + list=NULL; + mx_sort_linked_list(&list,obj_compare,obj_getnextptr); + assert(list==NULL); + + /* (9) -> (9) */ + + list=&o[9]; + mx_sort_linked_list(&list,obj_compare,obj_getnextptr); + assert(list==&o[9]); + assert(o[9].next==NULL); + + /* (9 8 7 6 5 4 3 2 1 0) -> (0 1 2 3 4 5 6 7 8 9) */ + + list=&o[0]; + for (int i=0;i<10;i++) { + o[i].i = 9-i; + } + mx_sort_linked_list(&list,obj_compare,obj_getnextptr); + assert(list==&o[9]); + for (int i=0;i<10;i++) { + assert(o[i].next == (i==0 ? NULL : &o[i-1])); + } + + /* (100 0 1 2 50 50 2 1 0 100) -> ( 0 0 1 1 2 2 50 50 100 100) stable */ + for (int i=0;i<10;i++) { + o[i].next= i==9 ? NULL : &o[i+1]; + } + list=&o[0]; + o[0].i=100; + o[1].i=0; + o[2].i=1; + o[3].i=2; + o[4].i=50; + o[5].i=50; + o[6].i=2; + o[7].i=1; + o[8].i=0; + o[9].i=100; + mx_sort_linked_list(&list,obj_compare,obj_getnextptr); + assert(list==&o[1]); + assert(o[1].next==&o[8]); + assert(o[8].next==&o[2]); + assert(o[2].next==&o[7]); + assert(o[7].next==&o[3]); + assert(o[3].next==&o[6]); + assert(o[6].next==&o[4]); + assert(o[4].next==&o[5]); + assert(o[5].next==&o[0]); + assert(o[0].next==&o[9]); + assert(o[9].next==NULL); +} + int main(int argc, char *argv[]) { test_mx_strskipwhitespaces(); @@ -469,5 +549,6 @@ int main(int argc, char *argv[]) test_mx_strvec_cachebug(); test_mx_strcat(); test_mx_cpuset(); + test_listsort(); return 0; }