From 24fe53991a97f70fc2586654b7f2f720516a9e0a Mon Sep 17 00:00:00 2001 From: Donald Buczek Date: Tue, 4 Jul 2017 09:26:28 +0200 Subject: [PATCH 01/20] test_mx_util: Avoid -Wparentheses warnings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add extra parentheses around assigments evaluated as conditions as suggested by -Wparentheses. This is to avoid CC test_mx_util.o test_mx_util.c: In function ‘test_mx_strskipwhitespaces’: test_mx_util.c:17:4: warning: suggest parentheses around assignment used as truth value [-Wparentheses] assert(s = mx_strskipwhitespaces(" abc ")); ^ test_mx_util.c:20:4: warning: suggest parentheses around assignment used as truth value [-Wparentheses] assert(s = mx_strskipwhitespaces("abc ")); ^ test_mx_util.c:23:4: warning: suggest parentheses around assignment used as truth value [-Wparentheses] assert(s = mx_strskipwhitespaces("")); ^ test_mx_util.c: In function ‘test_mx_strscan’: test_mx_util.c:311:5: warning: suggest parentheses around assignment used as truth value [-Wparentheses] assert(s = strdup("123 456 -789 246 abc")); ^ etc. --- test_mx_util.c | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/test_mx_util.c b/test_mx_util.c index a440ebf7..e127e570 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); From 8496f6aa52f5bda7b854be20eaeb15796cd02e2a Mon Sep 17 00:00:00 2001 From: Donald Buczek Date: Fri, 19 May 2017 13:50:35 +0200 Subject: [PATCH 02/20] mx_util: Add mx_sort_linked_list This is a general purpose routine to sort a single linked list of objects. It requires callbacks for comparison and for locating the next pointer in the object. --- mx_util.c | 27 +++++++++++++++++ mx_util.h | 4 +++ test_mx_util.c | 81 ++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 112 insertions(+) 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/test_mx_util.c b/test_mx_util.c index e127e570..05389f42 100644 --- a/test_mx_util.c +++ b/test_mx_util.c @@ -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; } From c956da72610889356a271dc5e6a0c06a178fb6f8 Mon Sep 17 00:00:00 2001 From: Donald Buczek Date: Fri, 19 May 2017 14:18:23 +0200 Subject: [PATCH 03/20] mxqd_control: Use generic sort function to sort users No functional change. --- mxqd_control.c | 59 ++++++++++++-------------------------------------- 1 file changed, 14 insertions(+), 45 deletions(-) diff --git a/mxqd_control.c b/mxqd_control.c index 419d6bfb..7a68d564 100644 --- a/mxqd_control.c +++ b/mxqd_control.c @@ -519,52 +519,21 @@ 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; - - for (ulist = server->users, uroot = NULL; ulist; ulist = unext) { - unext = ulist->next; - - ulist->next = NULL; - - if (!uroot) { - uroot = ulist; - continue; - } - - 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; - } - - ulist->next = current; + return + u1->global_slots_running > u2->global_slots_running ? 1 + : u1->global_slots_running < u2->global_slots_running ? -1 + : 0; +} - if (!uprev) { - uroot = ulist; - } else { - uprev->next = ulist; - } - break; - } - } +static struct mxq_user_list **get_users_nextptr(struct mxq_user_list *u) +{ + return &u->next; +} - server->users = uroot; +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); } From e10e3d00cbcdf1194355ef4477106caeac7a78d9 Mon Sep 17 00:00:00 2001 From: Donald Buczek Date: Fri, 19 May 2017 13:55:46 +0200 Subject: [PATCH 04/20] mxqd_config: Add server_sort_groups_by_priority Add a utility routing to sort the groups of all users of the server to their group priority. --- mxqd_control.c | 21 +++++++++++++++++++++ mxqd_control.h | 2 +- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/mxqd_control.c b/mxqd_control.c index 7a68d564..36814e26 100644 --- a/mxqd_control.c +++ b/mxqd_control.c @@ -537,3 +537,24 @@ 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); } + +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; +} + +static struct mxq_group_list **get_groups_nextptr(struct mxq_group_list *g) { + return &g->next; +} + +void server_sort_groups_by_priority(struct mxq_server *server) { + assert(server); + struct mxq_user_list *user; + + for (user=server->users;user;user=user->next) { + mx_sort_linked_list(&user->groups,cmp_groups_by_priority,get_groups_nextptr); + } +} 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 From d99d56286a70c255eb04def2248810859d5045e0 Mon Sep 17 00:00:00 2001 From: Donald Buczek Date: Fri, 19 May 2017 13:58:04 +0200 Subject: [PATCH 05/20] mxqd: Sort groups by priority When active groups are (re-)loaded from the database, we sort them by group priority. This implements group priority (mxqsub -P). --- mxqd.c | 1 + 1 file changed, 1 insertion(+) diff --git a/mxqd.c b/mxqd.c index 1a723c7b..933a8638 100644 --- a/mxqd.c +++ b/mxqd.c @@ -2239,6 +2239,7 @@ int load_running_groups(struct mxq_server *server) free(grps); server_remove_orphaned_groups(server); + server_sort_groups_by_priority(server); return total; } From ff2f49fafefea54d48973678907f8dccc4868bde Mon Sep 17 00:00:00 2001 From: Donald Buczek Date: Wed, 5 Jul 2017 09:25:02 +0200 Subject: [PATCH 06/20] mxqd: Use synchronous signals We intend to expand the functionality which can be triggered by signals, e.g. switch log level to debug or trigger a state dump. By processing signals synchronously, we are free to call non-reentrant functions and know, that our own data is not in a transient state. Block asynchronous signals and receive and process signals explicitly. Re-enable the asynchronous signals when the user process in initialized. --- mxqd.c | 82 ++++++++++++++++++++++++++++++++-------------------------- 1 file changed, 46 insertions(+), 36 deletions(-) diff --git a/mxqd.c b/mxqd.c index 933a8638..f3de16b0 100644 --- a/mxqd.c +++ b/mxqd.c @@ -58,6 +58,7 @@ 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 sigset_t all_signals; int mxq_redirect_output(char *stdout_fname, char *stderr_fname); void server_free(struct mxq_server *server); @@ -734,6 +735,8 @@ static int init_child_process(struct mxq_group_list *glist, struct mxq_job *job) reset_signals(); + sigprocmask(SIG_UNBLOCK,&all_signals,NULL); + passwd = getpwuid(group->user_uid); if (!passwd) { mx_log_err("job=%s(%d):%lu:%lu getpwuid(): %m", @@ -2303,29 +2306,31 @@ 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; - } - - if (sig == SIGQUIT) { - global_sigquit_cnt++; - return; - } - - if (sig == SIGUSR1) { - global_sigrestart_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 SIGCHLD: + mx_log_info("received sigchld"); + break; + default: + mx_log_warning("received signal %d (unexpected!)",sig); + break; } } @@ -2342,11 +2347,14 @@ int main(int argc, char *argv[]) int res; int fail = 0; + struct timespec poll_interval={0,0}; + siginfo_t siginfo; int saved_argc; _mx_cleanup_free_ char *saved_argv_str = NULL; _mx_cleanup_free_ char *saved_cwd = NULL; + sigfillset(&all_signals); /*** server init ***/ @@ -2391,14 +2399,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) { @@ -2412,6 +2413,11 @@ int main(int argc, char *argv[]) server_dump(server); 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(&all_signals,&siginfo,&poll_interval); + if (res>0) + process_signal(server,res,siginfo.si_int); + slots_returned = catchall(server); slots_returned += fspool_scan(server); @@ -2436,7 +2442,7 @@ int main(int argc, char *argv[]) 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); + poll_interval.tv_sec=1; continue; } @@ -2449,7 +2455,7 @@ int main(int argc, char *argv[]) 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); + poll_interval.tv_sec=7; continue; } @@ -2457,8 +2463,8 @@ int main(int argc, char *argv[]) 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; + poll_interval.tv_sec=3; continue; } else if (slots_started) { mx_log_info("slots_started=%lu :: Main Loop started %lu slots.", slots_started, slots_started); @@ -2468,11 +2474,11 @@ int main(int argc, char *argv[]) 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); + poll_interval.tv_sec=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); + poll_interval.tv_sec=3; } continue; } @@ -2488,6 +2494,7 @@ int main(int argc, char *argv[]) /* while not quitting and not restarting -> wait for and collect all running jobs */ mxq_daemon_set_status(server->mysql, daemon, MXQ_DAEMON_STATUS_TERMINATING); + poll_interval.tv_sec=1; while (server->jobs_running && !global_sigquit_cnt && !global_sigrestart_cnt && !fail) { slots_returned = catchall(server); slots_returned += fspool_scan(server); @@ -2515,7 +2522,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(&all_signals,&siginfo,&poll_interval); + if (res>0) + process_signal(server,res,siginfo.si_int); } mxq_daemon_shutdown(server->mysql, daemon); From 721974391f6424f40edcd4ee079cf80515902a88 Mon Sep 17 00:00:00 2001 From: Donald Buczek Date: Wed, 5 Jul 2017 15:05:06 +0200 Subject: [PATCH 07/20] mxqd: Remove obsolete signal() calls in reaper Now hat all signals are blocked, we don't need to ignore them. --- mxqd.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/mxqd.c b/mxqd.c index f3de16b0..e8449552 100644 --- a/mxqd.c +++ b/mxqd.c @@ -1061,12 +1061,6 @@ int reaper_process(struct mxq_server *server,struct mxq_group_list *glist, struc 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"); From 784dcbc125c3c6090e4778f05f7276a3d2f1fc87 Mon Sep 17 00:00:00 2001 From: Donald Buczek Date: Wed, 5 Jul 2017 12:25:24 +0200 Subject: [PATCH 08/20] mxqd: Remove reset_signals It is no longer necessary to reset signals, because we no longer change them. The signals may stay blocked for the reaper process and are explicitly unblocked for the user process. --- mxqd.c | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/mxqd.c b/mxqd.c index e8449552..f5964a01 100644 --- a/mxqd.c +++ b/mxqd.c @@ -701,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; @@ -733,8 +718,6 @@ 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); @@ -1059,8 +1042,6 @@ int reaper_process(struct mxq_server *server,struct mxq_group_list *glist, struc group = &glist->group; - reset_signals(); - res = setsid(); if (res < 0) { mx_log_warning("reaper_process setsid: %m"); From e64d665de4908e7f5f04bbd2162c1d49919d42e7 Mon Sep 17 00:00:00 2001 From: Donald Buczek Date: Wed, 5 Jul 2017 13:44:29 +0200 Subject: [PATCH 09/20] mxqd: Remove volatile sig_atomic_t type We process signals synchronously now. Change "volatile sig_atomic_t" to "int" type. --- mxqd.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mxqd.c b/mxqd.c index f5964a01..86abc683 100644 --- a/mxqd.c +++ b/mxqd.c @@ -53,10 +53,10 @@ #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; From 8660ae595099a8c0a95c3f9cfb69e0e0447b3e44 Mon Sep 17 00:00:00 2001 From: Donald Buczek Date: Wed, 5 Jul 2017 14:48:09 +0200 Subject: [PATCH 10/20] mxqd: Dump server state on SIGUSR2 q=10 A dump of server state can be triggerd by env kill -usr2 -q 10 mxqd --- mxqd.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/mxqd.c b/mxqd.c index 86abc683..bfff359d 100644 --- a/mxqd.c +++ b/mxqd.c @@ -1364,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) { @@ -2300,6 +2297,17 @@ static void process_signal(struct mxq_server *server,int sig,int extra) 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; + default: + mx_log_warning("received sigusr2 extra %d (unexpected!)",extra); + break; + } + break; case SIGCHLD: mx_log_info("received sigchld"); break; From 3a7b5f668f5db548c0bef4d0c7bb08602ea40eba Mon Sep 17 00:00:00 2001 From: Donald Buczek Date: Wed, 5 Jul 2017 15:36:09 +0200 Subject: [PATCH 11/20] mxqd: Change loglevel on SIGUSR2 q=20 or 21 The loglevel may be set to info with env kill -usr2 -q 21 mxqd and to info with env kill -usr2 -q 20 mxqd --- mxqd.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/mxqd.c b/mxqd.c index bfff359d..0006c372 100644 --- a/mxqd.c +++ b/mxqd.c @@ -2303,6 +2303,14 @@ static void process_signal(struct mxq_server *server,int sig,int extra) 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; From 447cc7f8784e355cd3837f290d63c5261757dce1 Mon Sep 17 00:00:00 2001 From: Donald Buczek Date: Wed, 5 Jul 2017 15:53:21 +0200 Subject: [PATCH 12/20] mxq_log: Do not deduplicate messages We intent to make loggin on info level less verbose so that we don'T need the deduplication here. At debug level we want to see all events as they happen. --- mxq_log.c | 29 +---------------------------- 1 file changed, 1 insertion(+), 28 deletions(-) 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; } From 6a15b2bba487b49ee8d2226e29011a8d6cf6c5a0 Mon Sep 17 00:00:00 2001 From: Donald Buczek Date: Wed, 5 Jul 2017 20:00:58 +0200 Subject: [PATCH 13/20] mxqd: Reduce logging at log level info in main loop --- mxqd.c | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/mxqd.c b/mxqd.c index 0006c372..14867cc0 100644 --- a/mxqd.c +++ b/mxqd.c @@ -2401,8 +2401,7 @@ int main(int argc, char *argv[]) if (server->recoveronly) fail = 1; - server_dump(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(&all_signals,&siginfo,&poll_interval); @@ -2415,11 +2414,6 @@ int main(int argc, char *argv[]) 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); @@ -2432,7 +2426,7 @@ int main(int argc, char *argv[]) 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)"); + mx_log_debug("Nothing to do"); poll_interval.tv_sec=1; continue; } @@ -2445,7 +2439,7 @@ int main(int argc, char *argv[]) } 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)."); + mx_log_debug("All slots running"); poll_interval.tv_sec=7; continue; } @@ -2453,7 +2447,7 @@ int main(int argc, char *argv[]) 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)."); + mx_log_info("no slots_started => we have users waiting for free slots"); slots_started = 0; poll_interval.tv_sec=3; continue; @@ -2464,11 +2458,11 @@ int main(int argc, char *argv[]) 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)."); + mx_log_debug("Tried Hard and nobody is doing anything."); poll_interval.tv_sec=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)."); + mx_log_debug("Tried Hard. But have done nothing."); poll_interval.tv_sec=3; } continue; From 82d7a3107416d24977c9836b77546a9b3bf5660e Mon Sep 17 00:00:00 2001 From: Donald Buczek Date: Wed, 5 Jul 2017 22:08:17 +0200 Subject: [PATCH 14/20] mxqd: Start jobs without main loop iteration Try to start as many jobs jobs as possible without going through a main loop iteration (database roundtrip). --- mxqd.c | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/mxqd.c b/mxqd.c index 14867cc0..ce78837d 100644 --- a/mxqd.c +++ b/mxqd.c @@ -2444,15 +2444,20 @@ int main(int argc, char *argv[]) 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"); - slots_started = 0; - poll_interval.tv_sec=3; - continue; - } else if (slots_started) { - mx_log_info("slots_started=%lu :: Main Loop started %lu slots.", slots_started, slots_started); + 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); + poll_interval.tv_sec=3; + continue; } if (!slots_started && !slots_returned && !global_sigint_cnt && !global_sigterm_cnt) { From 117c12b624d35499f5f36f2ccada089251009d4c Mon Sep 17 00:00:00 2001 From: Donald Buczek Date: Wed, 5 Jul 2017 20:21:48 +0200 Subject: [PATCH 15/20] mxqd: Change poll times Use 10 seconds everywhere to decrease the load on the database and races between the mxq daemons a bit. At the same time this increases the chance that multiple jobs of the same group are started on the same server, which is good (better use of caches, smaller failure surface). This is the maximum time a single server will need to react to database changes (mxqsub or mxqkill). Administrative signals will get immediate reaction. Finished user jobs will usually also get immediate reaction. However, this is not true for jobs we picked up from a previous daemon incarnation and which are not our children. If these jobs finish, we will not get a signal, so we need to look into the spool directory from time to time. This is another reason, why we need a timeout at all. Now that we want to use 10 seconds everywhere, we can make it a constant. --- mxqd.c | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/mxqd.c b/mxqd.c index ce78837d..3d8cbf49 100644 --- a/mxqd.c +++ b/mxqd.c @@ -2338,7 +2338,7 @@ int main(int argc, char *argv[]) int res; int fail = 0; - struct timespec poll_interval={0,0}; + static struct timespec poll_interval={10,0}; /* 10 seconds */ siginfo_t siginfo; int saved_argc; @@ -2427,7 +2427,6 @@ int main(int argc, char *argv[]) assert(!group_cnt); mxq_daemon_set_status(server->mysql, daemon, MXQ_DAEMON_STATUS_IDLE); mx_log_debug("Nothing to do"); - poll_interval.tv_sec=1; continue; } @@ -2440,7 +2439,6 @@ int main(int argc, char *argv[]) mxq_daemon_set_status(server->mysql, daemon, MXQ_DAEMON_STATUS_FULL); } mx_log_debug("All slots running"); - poll_interval.tv_sec=7; continue; } @@ -2456,7 +2454,6 @@ int main(int argc, char *argv[]) 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); - poll_interval.tv_sec=3; continue; } @@ -2464,11 +2461,9 @@ int main(int argc, char *argv[]) if (!server->jobs_running) { mxq_daemon_set_status(server->mysql, daemon, MXQ_DAEMON_STATUS_IDLE); mx_log_debug("Tried Hard and nobody is doing anything."); - poll_interval.tv_sec=15; } else { mxq_daemon_set_status(server->mysql, daemon, MXQ_DAEMON_STATUS_RUNNING); mx_log_debug("Tried Hard. But have done nothing."); - poll_interval.tv_sec=3; } continue; } @@ -2484,7 +2479,6 @@ int main(int argc, char *argv[]) /* while not quitting and not restarting -> wait for and collect all running jobs */ mxq_daemon_set_status(server->mysql, daemon, MXQ_DAEMON_STATUS_TERMINATING); - poll_interval.tv_sec=1; while (server->jobs_running && !global_sigquit_cnt && !global_sigrestart_cnt && !fail) { slots_returned = catchall(server); slots_returned += fspool_scan(server); From 0717a764f442eee3f540166a0d37cc2145f48d11 Mon Sep 17 00:00:00 2001 From: Donald Buczek Date: Wed, 5 Jul 2017 22:48:04 +0200 Subject: [PATCH 16/20] mxqd: Only process signals we expect We don't want spurious signals (like SIGWINCH) to trigger a new evaluation of the main loop. Only wait for signals we want to be processed. --- mxqd.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/mxqd.c b/mxqd.c index 3d8cbf49..8e6c9aae 100644 --- a/mxqd.c +++ b/mxqd.c @@ -2336,6 +2336,8 @@ 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 */ @@ -2347,6 +2349,14 @@ int main(int argc, char *argv[]) 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 ***/ saved_argc = argc; @@ -2404,7 +2414,7 @@ int main(int argc, char *argv[]) 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(&all_signals,&siginfo,&poll_interval); + res=sigtimedwait(&sigset,&siginfo,&poll_interval); if (res>0) process_signal(server,res,siginfo.si_int); @@ -2507,7 +2517,7 @@ int main(int argc, char *argv[]) global_sigint_cnt, global_sigterm_cnt); mx_log_debug("termination loop - wait for signals max %ld sec",poll_interval.tv_sec); - res=sigtimedwait(&all_signals,&siginfo,&poll_interval); + res=sigtimedwait(&sigset,&siginfo,&poll_interval); if (res>0) process_signal(server,res,siginfo.si_int); } From 3fe7c3dcf0db785b0007ee5b603cbf49d135560d Mon Sep 17 00:00:00 2001 From: Donald Buczek Date: Wed, 5 Jul 2017 22:51:40 +0200 Subject: [PATCH 17/20] mxqd: Change some messages from info to debug --- mxqd.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mxqd.c b/mxqd.c index 8e6c9aae..171bf159 100644 --- a/mxqd.c +++ b/mxqd.c @@ -1059,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); } @@ -1183,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); From adbe31210ad549393133520061b9c045e7b647b8 Mon Sep 17 00:00:00 2001 From: Donald Buczek Date: Wed, 5 Jul 2017 23:18:43 +0200 Subject: [PATCH 18/20] mxqdctl-hostconfig: Add debug commands Add commands killall quitall reloadall reloadall (=restartall) dumpall setdebugall setinfoall (=setnodebugall) --- mxqdctl-hostconfig.sh | 37 +++++++++++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) 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 From ed54cf5eeee34d5b240986941fddd2034b734a1b Mon Sep 17 00:00:00 2001 From: Donald Buczek Date: Thu, 6 Jul 2017 10:26:54 +0200 Subject: [PATCH 19/20] mxqd: Refactor main loop The server status if multiple times set from the main loop to IDLE,RUNNING,BACKFILL,CPUOPTIMAL or FULL based on server->slots_running, server->slots, server->threads_running. Factor this out into a separate function to avoid repetition. Add a call to this function before the main loop so that we immediately get a correct status after a server start. Simplify the loop code by setting server status at end of loop and avoid the continue pattern. As a side effect this also resolves a bug, that the server status was not updated in a loop iteration where new jobs were started. --- mxqd.c | 72 ++++++++++++++++++++++++++-------------------------------- 1 file changed, 32 insertions(+), 40 deletions(-) diff --git a/mxqd.c b/mxqd.c index 171bf159..8435e3bf 100644 --- a/mxqd.c +++ b/mxqd.c @@ -2325,6 +2325,25 @@ static void process_signal(struct mxq_server *server,int sig,int extra) } } +static void update_status(struct mxq_server *server) +{ + struct mxq_daemon *daemon = &server->daemon; + + 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); + } +} + int main(int argc, char *argv[]) { int group_cnt; @@ -2411,6 +2430,7 @@ int main(int argc, char *argv[]) if (server->recoveronly) fail = 1; + 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); @@ -2432,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_debug("Nothing to do"); - 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_debug("All slots running"); - continue; - } - - 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) { + 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; - } - - 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_debug("Tried Hard and nobody is doing anything."); - } else { - mxq_daemon_set_status(server->mysql, daemon, MXQ_DAEMON_STATUS_RUNNING); - mx_log_debug("Tried Hard. But have done nothing."); } - continue; } + update_status(server); } /*** clean up ***/ From f15abe0812b0a78b898a0b59c37017ea55f228b2 Mon Sep 17 00:00:00 2001 From: Donald Buczek Date: Thu, 6 Jul 2017 11:27:09 +0200 Subject: [PATCH 20/20] mxqd: Use return() instead of exit() This is just to avoid valgrind leak warnings. We have two variables with cleanup attribute in the scope of main, which are not freed, when we leave by exit() instead of running out of scope. ==10696== 21 bytes in 1 blocks are still reachable in loss record 1 of 3 ==10696== at 0x4C29AC6: malloc (vg_replace_malloc.c:299) ==10696== by 0x5E19449: strdup (strdup.c:42) ==10696== by 0x5E750DD: get_current_dir_name (getdirname.c:40) ==10696== by 0x409F64: main (mxqd.c:2383) ==10696== ==10696== 45 bytes in 1 blocks are still reachable in loss record 2 of 3 ==10696== at 0x4C29AC6: malloc (vg_replace_malloc.c:299) ==10696== by 0x40CFF9: mx_strvec_to_str (mx_util.c:1059) ==10696== by 0x409F58: main (mxqd.c:2382) --- mxqd.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/mxqd.c b/mxqd.c index 8435e3bf..7d188810 100644 --- a/mxqd.c +++ b/mxqd.c @@ -2553,6 +2553,5 @@ int main(int argc, char *argv[]) mx_log_info("cu, mx."); mx_log_finish(); - - exit(0); + return(0); }