Fixing multiple issues in "profctl" program accepted/tizen_5.5_unified accepted/tizen_5.5_unified_wearable_hotfix tizen_5.5 tizen_5.5_wearable_hotfix accepted/tizen/5.5/unified/20200519.155015 accepted/tizen/5.5/unified/wearable/hotfix/20201027.092116 accepted/tizen/unified/20200515.081433 submit/tizen/20200515.062155 submit/tizen_5.5/20200515.062629 submit/tizen_5.5_wearable_hotfix/20201026.1843010 submit/tizen_5.5_wearable_hotfix/20201027.114701
authorKirill Frolov <k.frolov@samsung.com>
Mon, 23 Sep 2019 16:36:35 +0000 (19:36 +0300)
committerAlexander Soldatov/Staff Engineer/AI Compiler Lab /SRR/Samsung Electronics <soldatov.a@samsung.com>
Fri, 25 Oct 2019 12:05:06 +0000 (15:05 +0300)
(related to program termination, and other):

Detailed list of changes:

1) profiling data pipe (`pipe_fd`) is opened in non-blocking mode (to
avoid blocking until external process starts) and made non-blocking for
reading;

2) start using raw file descriptor (`pipe_fd`) instead of libc file
(`pipef`) for reading end of data pipe;

3) control socket renamed from `control_socket` to `control_fd` (this
might be non socket, but any file) and made non-blocking for reading;

4) libc file `ctrl_file_in` isn't used anymore for reading control
socket (raw file descriptor is used directly);

5) libc file (`data_file_out`) isn't used anymore for writing profiling
data (raw file descriptor is used directly);

6) global variable `global_stop` eliminated, instead pipe `stop_pipe` is
used for stopping parallel threads;

7) second pipe `chld_pipe` used to process SIGCHLD synchronously (in
main loop);

8) flags which shows that particular thread terminated not used anymore;

9) MAIN CHANGE: not using anymore c-library functions from reading data
from non-blocking pipes or sockets (`pipe_fd` and `control_fd`), using
read(2) syscall with select(2) -- this allows correctly terminate
threads which blocks on reading from pipe or socket;

10) when copying data from data pipe (`pipe_fd`) to socket
(`data_socket`), using splice(2) system call (linux specific) to avoid
unnecessary copying via userspace;

11) external process starts after all sockets opened, but not before (to
simplify error handling), also stdin/stdout not closed begore exec, but
replaced to "/dev/null", stderr is never closed to allow errors
reporting;

12) in main loop (in main() function) only three reasons to terminate
application exists:

   * closing control file/socket/pipe;
   * "exit" command sent in control pipe;
   * external process termination.

If external process closes data pipe, if data copying thread fails, if
statistics thread fails -- none of this will terminate application, it
will wait until one of the three events listed above.

13) rewritten thread termination logic (now using just pthread_join() in
regular way);

14) rewritten code for extern program termination logic;

15) added -Wall and -Wextra CMakeLists.txt, fixed warnings;

16) added "--help" option to command line arguments;

17) fixed issue with wrong terminal settings on exit;

18) commented out code which closes stdin/stdout/stderr and calls
raise(SIGKILL) -- I not understood why this need, looks like some dirty
trick to avoid other bugs.

profctl.c file modified (see below)

Detailed list of changes:

1) code changed accordig code review comments;
2) reporting of dotnet-launcher exit code via socket;
3) fixed bug (SEGFAULT on exit when no stat thread started);
4) fixed few issues with non-blocking IO;
5) added FD_CLOEXEC for most files;
6) removed terminal-controlling functions (not needed);
7) removed close_files_and_sockets() function (not needed, because of using FD_CLOSEXEC option);
8) watching for helper process termination and log errors;
9) fixed issues with unexpected helper process termination;
10) fixed bugs/warnings.

profctl.c: added more comments, no program's logic changed.

profctl.c: redirect stderr of child processes to the log.

profctl.c: fixed bug -- 100% cpu load in data copying thread.

profctl.c: increase pipe size for helper process to avoid unwanted delays

profctl.c: setting pipe size for heaptrack_interpret

Increasing pipe buffer size at OS level might avoid program stops in
cases, when heaptrack_interpret works too slow.

Previously logger thread might hang if child process doesn't close other
end of the pipe.

CMakeLists.txt
logging.c
logging.h
proc_stat.c
profctl.c

index 456fe70..035c384 100644 (file)
@@ -7,6 +7,7 @@ FOREACH(flag ${${PROJECT_NAME}_CFLAGS})
     SET(EXTRA_CFLAGS "${EXTRA_CFLAGS} ${flag}")
 ENDFOREACH(flag)
 
+SET(EXTRA_CFLAGS "${EXTRA_CFLAGS} -Wall -Wextra")
 SET(EXTRA_CFLAGS "${EXTRA_CFLAGS} -pthread -ggdb")
 #SET(EXTRA_CFLAGS "${EXTRA_CFLAGS} -fvisibility=hidden")
 SET(EXTRA_CFLAGS "${EXTRA_CFLAGS} -fdata-sections -ffunction-sections")
index ede07e8..bd15f8f 100644 (file)
--- a/logging.c
+++ b/logging.c
 #include <stdarg.h>
 #include <stdlib.h>
 #include <string.h>
+#include <limits.h>
 #include <unistd.h>
+#include <signal.h>
 #include <sys/syscall.h>
 #include <sys/types.h>
 
 #include "logging.h"
 
-extern int log_pid = 0;
-extern int log_tid = 0;
+/* Global variables which determine logger behaviour: */
+int log_pid = 0;       /* add PID to each log line */
+int log_tid = 0;       /* add TID to each log line */
 
+/* file name to which logs will be written */
 static FILE *log_file = NULL;
 
 static pthread_mutex_t log_lock;
 
+static int log_pipe[2] = { -1, -1 };  /* pipe used to receive logs (as stderr) from child processes */
+static pthread_t log_thread;         /* thread which copies from log_pipe to log_file */
+
+/* Function returns file descriptor suitable for using as stdout/stderr for child
+ * processes (messages written to this file will be saved to log).
+ * Function might return -1 if no such file descriptor available. */
+int get_log_fd(void)
+{
+       return log_pipe[1];
+}
+
+/* dummy signal handler */
+void sighandler(int signo)
+{
+       (void)signo;  /* avoid "unused variable" warning */
+}
+
+/* log speccified time, should be called under mutex. */
+static void log_time(const struct tm *tm, int msec)
+{
+       fprintf(log_file, "%02d-%02d-%02d %02d:%02d:%02d.%03d ",
+               tm->tm_year + 1900 - 2000, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, msec);
+}
+
+/* log_message() for child processes, should be called under mutex. */
+static void log_child(const struct tm *tm, int msec, const char *str, size_t size)
+{
+       static const char prefix[] = "[CHILD] ";
+       log_time(tm, msec);
+       fwrite(prefix, sizeof(prefix) - 1, 1, log_file);
+       fwrite(str, size, 1, log_file);
+       putc('\n', log_file);
+}
+
+/* Logger thread function, which reads lines from log_pipe and logs via logger. */
+static void *log_thread_func(void *arg)
+{
+       (void)arg;  /* avoiding "unused variable" warning */
+
+       char buffer[32*LINE_MAX];
+       char *end = buffer;
+       ssize_t rsize;
+
+       /* can get errno = EINTR here */
+       while (1)
+       {
+               rsize = read(log_pipe[0], end, sizeof(buffer) - (end - buffer));
+               if (rsize == 0) break;  /* EOF */
+               if (rsize < 0) {
+                       if (errno != EINTR) {
+                               log_system_error("log_pipe");
+                               break;
+                       }
+                       /* other side can close log_pipe[1] as termination request */
+                       if (log_pipe[1] == -1) break;
+                       continue;
+               }
+
+               pthread_mutex_lock(&log_lock);
+
+               struct tm tm;
+               int millisec;
+               get_current_time(NULL, &tm, &millisec);
+
+               char *begin = buffer;
+               char *pos = end;
+               end += rsize;
+               char *nl;
+               /* split buffer contents to line and output each complete line */
+               while (nl = memchr(pos, '\n', end - pos), nl != NULL) {
+                       log_child(&tm, millisec, begin, nl - begin);
+                       begin = pos = nl + 1;
+               }
+               if (begin > buffer) {
+                       /* move unprocessed tail (not complete line)
+                        * to the beginning of the bufffer */
+                       size_t tail = end - begin;
+                       end = buffer + tail;
+                       memmove(buffer, begin, tail);
+               }
+               else if (end - begin >= (long)sizeof(buffer)) {
+                       /* overflow, flush buffer */
+                       log_child(&tm, millisec, buffer, sizeof(buffer));
+                       end = buffer;
+               }
+
+               fflush(log_file);
+               pthread_mutex_unlock(&log_lock);
+       }
+
+       return NULL;
+}
+
 int log_init()
 {
        if (pthread_mutex_init(&log_lock, NULL) != 0) {
@@ -25,11 +122,41 @@ int log_init()
                return -1;
        }
        log_file = stderr;
+
+       /* enable syscalls interrupting for SIGUSR2 */
+       struct sigaction sa;
+       sa.sa_handler = sighandler;
+       sigemptyset(&sa.sa_mask);
+       sa.sa_flags = 0;
+       sigaction(SIGUSR2, &sa, NULL);
+
+       /* create separate thread for logging from child processes */
+       log_pipe[0] = log_pipe[1] = -1;
+       if (-1 == pipe(log_pipe))
+               perror("pipe");
+       else {
+               int err = pthread_create(&log_thread, NULL, log_thread_func, NULL);
+               if (err != 0) {
+                       fprintf(stderr, "pthread_create: %s\n", strerror(err));
+                       close(log_pipe[0]), log_pipe[0] = -1;
+                       close(log_pipe[1]), log_pipe[1] = -1;
+               }
+       }
+
        return 0;
 }
 
 void log_uninit()
 {
+       /* terminate logging thread */
+       if (log_pipe[0] != -1) {
+               /* Note, program will hang here if child processes still not terminated. */
+               close(log_pipe[1]), log_pipe[1] = -1;
+               pthread_kill(log_thread, SIGUSR2);
+               pthread_join(log_thread, NULL);
+               close(log_pipe[0]), log_pipe[0] = -1;
+       }
+
        if (log_file != NULL && log_file != stderr) {
                fclose(log_file);
                log_file = NULL;
@@ -94,8 +221,7 @@ static void log_current_time()
        struct tm tm;
        int millisec;
        get_current_time(NULL, &tm, &millisec);
-       fprintf(log_file, "%02d-%02d-%02d %02d:%02d:%02d.%03d ",
-               tm.tm_year + 1900 - 2000, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec, millisec);
+       log_time(&tm, millisec);
 }
 
 static void log_current_data()
@@ -156,6 +282,7 @@ static void log_prefixed_system_error(FILE *file, int error_code, const char *pr
 
        char buf[1024] = {0};
        char *errno_msg;
+       (void)error_code;
 #if (_POSIX_C_SOURCE >= 200112L || _XOPEN_SOURCE >= 600) && ! defined(_GNU_SOURCE)
        int ret = strerror_r(error_code, buf, sizeof(buf));
        errno_msg = ret == 0 ? buf : "<strerror_r failure>";
index 9d777b6..794ae8c 100644 (file)
--- a/logging.h
+++ b/logging.h
@@ -6,6 +6,8 @@
 extern int log_pid;
 extern int log_tid;
 
+int get_log_fd(void);
+
 int log_init();
 void log_uninit();
 void log_set_file(FILE *file);
index ead44a1..fa2644a 100644 (file)
@@ -11,7 +11,7 @@
 #include "proc_stat.h"
 #include "logging.h"
 
-extern volatile int global_stop;
+extern int stop_pipe[];
 
 extern int is_running(int pid);
 
@@ -197,12 +197,31 @@ static void write_proc_stat(FILE *stat_file_out, int pid, int timeout_usec, int
        }
 
        const int line_buf_size = 256;
-       ssize_t slen, plen, mtlen, mflen;
+       size_t slen, plen, mtlen, mflen;
        char *sline = malloc(slen = line_buf_size);
        char *pline = malloc(plen = line_buf_size);
        char *mtline = malloc(mtlen = line_buf_size);
        char *mfline = malloc(mflen = line_buf_size);
-       while(!global_stop) {
+
+       struct timeval tv = { 0, 0 };
+       while (1) {
+               fd_set fdset;
+               FD_ZERO(&fdset);
+               FD_SET(stop_pipe[0], &fdset);
+
+               int nr = select(stop_pipe[0] + 1, &fdset, NULL, NULL, &tv);
+               if (nr == -1) {
+                       if (errno == EINTR) continue;
+                       log_system_error("select");
+                       abort();
+               }
+
+               if (nr > 0)
+                       break;  // termination request
+
+               // timeout occurs
+               tv = (struct timeval){ timeout_usec / 1000000, timeout_usec % 1000000 };
+
                char *stats[5];
                char *pstats[18];
                char *mt[3];
@@ -275,8 +294,6 @@ static void write_proc_stat(FILE *stat_file_out, int pid, int timeout_usec, int
                        log_system_error("cannot write stat");
                        break;
                }
-
-               usleep(timeout_usec);
        }
        free(sline);
        free(pline);
index 525ef82..1ee9760 100644 (file)
--- a/profctl.c
+++ b/profctl.c
@@ -1,9 +1,5 @@
 #define _GNU_SOURCE
 
-//#ifndef TIZEN
-//#define TIZEN 1
-//#endif
-
 #include <errno.h>
 #include <getopt.h>
 #include <signal.h>
@@ -13,10 +9,7 @@
 #include <string.h>
 #include <pthread.h>
 #include <unistd.h>
-
-#if TIZEN
-#include <termios.h>
-#endif /* TIZEN */
+#include <limits.h>
 
 #include <sys/time.h>
 #include <sys/types.h>
 #include <sys/xattr.h>
 #include <netinet/in.h>
 #include <fcntl.h>
+#include <sys/ioctl.h>
 
 #include "logging.h"
 #include "proc_stat.h"
 
-static int verbose = 0;
-static int doinfo = 0;
-static int stat_period_usec = 500000; // 0.5 seconds
-#if TIZEN
-static struct termios sterm;
-#endif /* TIZEN */
-
-static int app_pid = -1;
-static int cmd_pid = -1;
-static int cmd_idx = -1;
-static char *pname = NULL;
-static char *ename = NULL;
-static char *oname = NULL;
-
-static int isPipeOwner = 0;
-
-static int controlPort = -1;
-static int dataPort = -1;
-static int statPort = -1;
-static int control_socket = -1;
-static int data_socket = -1;
-static int stat_socket = -1;
-static FILE *ctrl_file_in = NULL;
-static FILE *ctrl_file_out = NULL;
-static FILE *data_file_out = NULL;
-static FILE *stat_file_out = NULL;
+/* Options which can be tuned via command line: */
+static int verbose = 0;                        /* 1: print more verbose messages in log (-v) */
+static int doinfo = 0;                 /* 1: enable perdiodic printing of process statistics (-i) */
+static int stat_period_usec = 500000;  /* uSec, period in which statistics is printed, default 0.5 seconds (-t) */
+
+/* Variables used by program */
+static int app_pid = -1;  /* pid of process, with which profctl works (usually same as cmd_pid) */
+static int cmd_pid = -1;  /* pid of process executed by profctl (-1 when attaching to existing process) */
+static int cmd_idx = -1;  /* index of last profctl's argument in argv[] array (rest passed to executed process) */
+static int exe_pid = -1;  /* pid of "helper" process (which processes output of profiler) */
+
+/* Options which can be specified in command line: */
+static char *pname = NULL;     /* named pipe file name (-p) */
+static char *ename = NULL;     /* helper executable name (-e), one which processes output of the profiler */
+static char *oname = NULL;     /* log file name (-o) */
+
+static int isPipeOwner = 0;    /* set to one by option "-w", allows creation and deleting fifo file */
 
+/* Note, port numbers can be set via command line, by default no TCP ports opened. */
+static int controlPort = -1;   /* TCP port used to give commands to profctl */
+static int dataPort = -1;      /* TCP port used to output data from the profiler */
+static int statPort = -1;      /* TCP port used to output process statistics */
+
+static int control_fd = -1;    /* file or socket used to give commands to profctl (can't be not opened) */
+static int data_socket = -1;   /* socket, which is used to output data from the profiler (might be not opened) */
+static int stat_socket = -1;   /* socket, which is used to output process statistics (might be not opened) */
+static FILE *ctrl_file_out = NULL;  /* write-only file associated with control_fd */
+static FILE *stat_file_out = NULL;  /* write-only file associated with stat_socket */
+
+/* Default PIPE size for new created pipes (1 MByte) */
+#define DEFAULT_PIPE_SIZE ((size_t)1 * 1024 * 1024)
+
+/* Maximal amount of additional environment variables, which can be set via -E option. */
 #ifndef ADDITIONAL_ENV_SIZE
 #define ADDITIONAL_ENV_SIZE 128
 #endif
 
-static int env_mode = 0;
+/* Values of the additional environment variables, which should be set in fork'ed process before exec. */
 static char *additional_env[ADDITIONAL_ENV_SIZE];
-static int additional_env_index = 0;
+static int additional_env_index = 0;    /* number of variables which contained in `additional_env' */
 
-volatile int global_stop = 0;
+int stop_pipe[2] = { -1, -1 };         /* pipe used to stop all threads */
+static int chld_pipe[2] = { -1, -1 };  /* pipe used to process SIGCHLD signal */
 
+/* Function returns non-zero value, if process `pid' still exists, */
 int is_running(int pid)
 {
        return kill(pid, 0) == 0;
@@ -85,20 +87,50 @@ static struct option long_options[] = {
        {"data",        required_argument, 0, 'd'},
        {"stat",        required_argument, 0, 's'},
        {"env",         optional_argument, 0, 'E'},
+       {"help",        no_argument,       0, 'h'},
        {0, 0, 0, 0}
 };
 
-static volatile int data_output_thread_runs = 0;
-static volatile int command_loop_thread_runs = 0;
-static volatile int proc_stat_thread_runs = 0;
+/* Thread, which periodically prints process statistics -- will not be created
+ * (and `proc_stat_thread_id' will be uninitialized) if `doinfo' isn't set to nonzero value. */
 static pthread_t proc_stat_thread_id;
 
-static FILE *pipef;
-
+/* Named fifo or unnamed pipe, which is used to receive data from profiled process:
+ *
+ *   * for "coreprofiler" writing end of the fifo is opened by profiling
+ *      process (by coreprofiler.so library), reading end is opened by this program;
+ *
+ *   * for "heaptrack" writing end of the fifo is opened by profiling process
+ *     (by libprofiler.so library), reading end is opened by this program and passed
+ *     to "heaptrack_interpret" helper process as standard input, standart output of
+ *     helper process is redirected to writing end of unnamed pipe, and reading end
+ *     of this pipe is opened by this program.
+ *
+ * In both cases, "procfctl" should just copy data from named fifo (for coreprofiler)
+ * or unnamed pipe (heaptrack) to data socket, if it is opened. This (`pipe_fd')
+ * variable contains file descriptor, from which "profctl" should read data.
+ */
+static int pipe_fd = -1;
+
+/* Forward declaration, see description in definition. */
 static int openPort(int port);
 
+
+/* This function runs separate thread, which reads data (as described above) from
+ * `pipe_fd' and just copies it to `data_socket'. Thread terminates in one of the
+ * three cases:
+ *
+ *   * then EOF condition reached in `pipe_fd' (which means, that other party is
+ *     closed writing end of the fifo;
+ *
+ *   * then main thread signals via `stop_pipe' to terminate all child threads;
+ *
+ *   * then `data_socket' isn't opened.
+ */
 static void *data_output_thread(void *arg)
 {
+       (void)arg;  // avoid "unused variable" warning
+
        if (dataPort > 0)
        {
                data_socket = openPort(dataPort);
@@ -106,97 +138,103 @@ static void *data_output_thread(void *arg)
                        log_error("cannot open data port");
                        return NULL;
                }
-               data_file_out = fdopen(data_socket, "w");
-               if (data_file_out == NULL) {
-                       log_system_error("fdopen(data,w)");
-                       return NULL;
-               }
-               if (fprintf(data_file_out, "ready\n") < 0 || fflush(data_file_out) != 0) {
+
+               static const char handshake[] = "ready\n";
+               if (write(data_socket, handshake, sizeof(handshake) - 1) <= 0) {
                        log_system_error("cannot write 'ready' prompt to data stream");
                        return NULL;
                }
        }
 
-       do
-       {
-               int linesRead = 0;
-
-               char *buffer = NULL;
-               size_t lsize = 0;
-               while (!global_stop) {
-                       if (getline(&buffer, &lsize, pipef) == -1) {
-                               if (feof(pipef)) {
-                                       if (verbose) {
-                                               log_message("pipe eof");
-                                       }
-                               }
-                               else {
-                                       log_system_error("cannot read data from pipe");
-                               }
+       /* Copy data from `pipe_fd` to `data_socket`, break on error
+        * or when `stop_pipe[0]` becomes readable. Variable `delay' used
+        * to introduce short delay before each next reading of `pipe_fd' and
+        * increase throughput in this way (delay allows accumulate bigger amount
+        * of data to copy, so overhead on calling read/write functions will be less). */
+       const int ReadThreshold = getpagesize();
+       int no_fionread = 0;
+       int delay = 0;
+       int const maxfd = (pipe_fd > stop_pipe[0] ? pipe_fd : stop_pipe[0]) + 1;
+       while (1) {
+               fd_set fdset;
+               struct timeval tv, *tp;
+
+               FD_ZERO(&fdset);
+               FD_SET(stop_pipe[0], &fdset);
+
+               // introduce short delay before reading pipe (to increase throughput)
+               if (delay) {
+                       tv = (struct timeval){ 0, 10000 };  // 10msec
+                       tp = &tv;
+                       delay = 0;
+               } else {
+                       FD_SET(pipe_fd, &fdset);
+                       tp = NULL;
+               }
+
+               int nr = select(maxfd, &fdset, NULL, NULL, tp);
+
+               if (nr == -1) {
+                       if (errno == EINTR) continue;
+                       log_system_error("select");
+                       abort();
+               }
+
+               if (nr == 0) continue; // timeout
+
+               if (FD_ISSET(stop_pipe[0], &fdset))
+                       break; // termination request
+
+               if (FD_ISSET(pipe_fd, &fdset)) {
+                       // copy data from pipe to output socket
+                       ssize_t res = splice(pipe_fd, NULL, data_socket, NULL, INT_MAX, SPLICE_F_MOVE);
+                       if (res == -1) {
+                               log_system_error("splice(pipe_fd, data_socket)");
                                break;
                        }
-                       ++linesRead;
-                       if (fputs(buffer, data_file_out) < 0) {
-                               log_message("cannot write data");
+
+                       if (res == 0) {
+                               if (verbose) log_message("pipe eof or data socket error");
                                break;
                        }
-               }
-               free(buffer);
 
-               if (verbose) {
-                       log_message("output thread exits (%d lines read from pipe)", linesRead);
+                       if (! no_fionread) {
+                               int nbytes;
+                               if (ioctl(pipe_fd, FIONREAD, &nbytes) == -1) {
+                                       no_fionread = 1;
+                                       log_system_error("ioctl(FIONREAD)");
+                               }
+                               else if (nbytes >= 0 && nbytes < ReadThreshold) {
+                                       delay = 1;
+                               }
+                       }
                }
-       } while (0); // one-iteration loop
-       data_output_thread_runs = 0;
-       global_stop = 1;
-       return NULL;
-}
-
-static void close_files_and_sockets()
-{
-       if (ctrl_file_in != NULL && ctrl_file_in != stdin) {
-               fclose(ctrl_file_in);
-       }
-       if (ctrl_file_out != NULL && ctrl_file_out != stdout) {
-               fclose(ctrl_file_out);
-       }
-       if (data_file_out != NULL && data_file_out != stdout) {
-               fclose(data_file_out);
-       }
-       if (stat_file_out != NULL && stat_file_out != stdout) {
-               fclose(stat_file_out);
-       }
-       if (control_socket >= 0) {
-               close(control_socket);
-       }
-       if (data_socket >= 0) {
-               close(data_socket);
        }
-       if (stat_socket >= 0) {
-               close(stat_socket);
+
+       if (verbose) {
+               log_message("output thread exits");
        }
+
+       return NULL;
 }
 
+
+/* This function is registered with atexit(3) and called on program termination. */
 static void finish()
 {
        log_message("exit handler called");
-#if TIZEN
-       if (controlPort < 0) {
-               tcsetattr(0, TCSANOW, &sterm);
-       }
-#endif /* TIZEN */
-       if (pipef != NULL) {
-               fclose(pipef);
+       if (pipe_fd != -1) {
+               close(pipe_fd), pipe_fd = -1;
                if (isPipeOwner) {
                        unlink(pname);
                }
        }
-       close_files_and_sockets();
-       log_message("=== finished ===");
        log_uninit();
-       kill(getpid(), SIGKILL);
 }
 
+/* This function checks, that command line option is not already applied and assigns
+ * value (c-string) to `*value'. In case if option is already assigned, the program
+ * terminates with error message. */
 static void CheckValue(char **value, const char *info)
 {
        if (value[0] != NULL) {
@@ -206,12 +244,15 @@ static void CheckValue(char **value, const char *info)
        value[0] = optarg;
 }
 
+/* Function parses command line options and assigns variables appropriately.
+ * Function might return -1 in case of error (can't parse options). */
 static int process_option(int argc, char **argv)
 {
        int option_index;
        int result = -1;
+       static int env_mode = 0; // should be init once, process_option called iterative.
 
-       result = getopt_long(argc, argv, "-p:ve:o:it:wc:d:s:E::",
+       result = getopt_long(argc, argv, "-p:ve:o:it:wc:d:s:E::h",
                long_options, &option_index);
 
        if (result == 1) {
@@ -263,6 +304,26 @@ static int process_option(int argc, char **argv)
                        cmd_idx = optind;
                }
                return -1;
+
+       case 'h':
+               printf("Usage: %s [options...] [--] [command-to-exec [args...]]\n", argv[0]);
+               puts("options are following:\n"
+                       "-p, --pipe <pipe-name>         profiling output (data pipe)\n"
+                       "-e, --exec <path>              execute program to create data pipe\n"
+                       "-o, --error <log-filename>     logging to specified file\n"
+                       "-v, --verbose                  increase verbosity\n"
+                       "-i, --info                     dump process statistics\n"
+                       "-t, --timeout <msec>           statistic printing period (0.5 sec default)\n"
+                       "-w, --pipe-owner               unlink and recreate data pipe\n"
+                       "-c, --control <port>           control/command TCP port\n"
+                       "-d, --data <port>              profiling data output TCP port\n"
+                       "-s, --stat <port>              statistics TCP port\n"
+                       "-E, --env <env-var=value>      assign additional environment variable\n"
+                       "-h, --help                     print this help\n"
+                       "\n"
+                       "Note, options -p (--pipe) and -e (--exec) are mutually exclusive.\n");
+
+               exit(EXIT_SUCCESS);
        default:
                // NOTE: should not be reachable
                abort();
@@ -270,6 +331,9 @@ static int process_option(int argc, char **argv)
        return 0;
 }
 
+
+/* This function executes function `func' with argument `arg' in separate thread.
+ * In case of error, if thread can't be created, error is logged and program aborts. */
 static pthread_t SimpleThread(void* (*func)(void *), void *arg)
 {
        pthread_attr_t attr;
@@ -280,23 +344,65 @@ static pthread_t SimpleThread(void* (*func)(void *), void *arg)
        pthread_t thread;
        if (pthread_create(&thread, &attr, func, arg) != 0) {
                log_system_error_and_exit("pthread_create");
+               abort();
        }
 
        return thread;
 }
 
-// TODO: should be refactored: use separated launcher
+
+/* Function sets pipe size (in bytes), which is close to the required (but
+ * might be less, because of OS limits).
+ * Function returns actual pipe size or -1 in case of error. */
+static int set_pipe_size(int fd, int pipe_size)
+{
+       int result;
+       while (1) {
+               result = fcntl(fd, F_SETPIPE_SZ, pipe_size);
+               if (result == -1) {
+                       if (errno == EPERM) {
+                               pipe_size = pipe_size / 2;
+                               if (pipe_size >= PIPE_BUF) continue;
+                       }
+                       log_system_error("fcntl(F_SETPIPE_SZ)");
+               }
+               break;
+       }
+       return result;
+}
+
+
+/* Function opens named fifo or unnamed pipe and assigns `pipe_fd' variable,
+ * logic is depending on command line options. See `pipe_fd' description above
+ * for the details.
+ *
+ * Function might return -1 in case of error (if pipe can't be opened).
+ *
+ * Named fifo (for "coreprofiler") is opened in non-blocking mode, so later
+ * it can be read only via select(2) (because after first call to read(2), if
+ * other end of the fifo still not opened, you will get EOF's constantly).
+ *
+ * Unnamed pipe (for "heaptrack") is opened in non-blocking mode too, but
+ * named pipe (which is read by helper process, "heaptrack_interpret") should
+ * only be opened in blocking mode, because helper process have no mention
+ * how pipe is opened and just reads is via reagular blocking functions.
+ */
 static int openFileProcess()
 {
        if (ename == NULL) {
-               pipef = fopen(pname, "r");
-               if (pipef == NULL) {
-                       log_system_error("fopen(%s)", pname);
+               pipe_fd = open(pname, O_RDONLY | O_NONBLOCK);
+               if (pipe_fd == -1) {
+                       log_system_error("open(%s)", pname);
                        return -1;
                }
                if (verbose) {
                        log_message("pipe %s opened for read", pname);
                }
+               fcntl(pipe_fd, F_SETFD, fcntl(pipe_fd, F_GETFD, 0) | FD_CLOEXEC);
+
+               int pipesz = set_pipe_size(pipe_fd, DEFAULT_PIPE_SIZE);
+               if (verbose && pipesz > 0) log_message("set pipe size to %d bytes", pipesz);
+
                return 0;
        }
 
@@ -307,7 +413,7 @@ static int openFileProcess()
                return -1;
        }
 
-       /* We must start interpreter process */
+       /* Create pipe between interpreter process and profctl. */
        int pipefd[2];
        if (pipe(pipefd)) {
                log_system_error("pipe");
@@ -318,51 +424,59 @@ static int openFileProcess()
                log_message("pipefd: [%d, %d]", pipefd[0], pipefd[1]);
        }
 
-       int id = open(pname, O_RDONLY);
-       if (id < 0) {
-               log_system_error("open(%s)", pname);
-               return -1;
-       }
+       int pipesz = set_pipe_size(pipefd[1], DEFAULT_PIPE_SIZE);
+       if (verbose && pipesz > 0) log_message("set pipe size to %d bytes", pipesz);
 
-       if (verbose) {
-               log_message("pname id: %d", id);
-       }
-
-       int pid = fork();
-       if (pid == -1) {
+       exe_pid = fork();
+       if (exe_pid == -1) {
                log_system_error("fork");
                return -1;
        }
 
-       if (pid == 0) {
+       if (exe_pid == 0) {
                /* Child branch */
 
                if (close(pipefd[0]) != 0) {
                         // not critical
                        log_system_error("CHILD close(%d)", pipefd[0]);
                }
-               if (dup2(pipefd[1], 1) == -1) { /* stdout */
+
+               if (dup2(pipefd[1], STDOUT_FILENO) == -1) { /* stdout */
                        log_system_error("CHILD dup2(%d, 1)", pipefd[1]);
                        return -1;
                }
-               if (dup2(id, 0) == -1) { /* stdin */
+               close(pipefd[1]);
+
+               /* here child hangs until libprofiler.so not opens writing end of the named fifo */
+               int id = open(pname, O_RDONLY);
+               if (id < 0) {
+                       log_system_error("CHILD open(%s)", pname);
+                       _exit(EXIT_FAILURE);
+               }
+
+               int pipesz = set_pipe_size(id, DEFAULT_PIPE_SIZE);
+               if (verbose && pipesz > 0) log_message("set pipe size to %d bytes", pipesz);
+
+               if (dup2(id, STDIN_FILENO) == -1) { /* stdin */
                        log_system_error("CHILD dup2(%d, 0)", id);
                        return -1;
                }
-               if (verbose) {
-                       log_message("CHILD close(%d); dup2(%d, 1); dup2(%d, 0)", pipefd[0], pipefd[1], id);
-               }
-               close_files_and_sockets();
-               if (verbose) {
-                       log_message("CHILD closed files and sockets");
+               close(id);
+
+               /* redirect stderr to log */
+               int fd = get_log_fd();
+               if (fd >= 0) {
+                       if (dup2(fd, STDERR_FILENO) == -1)
+                               log_system_error("CHILD dup2(%d, 2)", fd);
                }
+
                if (verbose) {
                        log_message("CHILD calling execl(%s)", ename);
                }
                log_uninit();
                execl(ename, ename, NULL);
                log_system_error("CHILD execl(%s)", ename); // execl returns only in case of error
-               return -1;
+               _exit(EXIT_FAILURE);
        }
 
        /* Parent */
@@ -371,24 +485,20 @@ static int openFileProcess()
                log_system_error("PARENT close(%d)", pipefd[1]);
        }
 
-       if (close(id) != 0) {
-               // not critical
-               log_system_error("PARENT close(%d)", id);
-       }
-
-       pipef = fdopen(pipefd[0], "r");
-       if (pipef == NULL) {
-               log_system_error("PARENT fdopen(%d, r)", pipefd[0]);
-               return -1;
-       }
+       pipe_fd = pipefd[0];
+       fcntl(pipe_fd, F_SETFL, fcntl(pipe_fd, F_GETFL, 0) | O_NONBLOCK); /* make input non blocking */
 
        if (verbose) {
-               log_message("PARENT close(%d); close(%d); fdopen(%d, r)", pipefd[1], id, pipefd[0]);
+               log_message("PARENT close(%d); fdopen(%d, r)", pipefd[1], pipefd[0]);
        }
 
        return 0;
 }
 
+
+/* Function starts listen on TCP port `port' and accepts exactly one connection to this port,
+ * function returns file descriptor for socket associated with connections.
+ * In case of error function might return -1. */
 static int openPort(int port)
 {
        int sock = socket(AF_INET, SOCK_STREAM, 0);
@@ -396,6 +506,7 @@ static int openPort(int port)
                log_system_error("socket");
                return -1;
        }
+       fcntl(pipe_fd, F_SETFD, fcntl(pipe_fd, F_GETFD, 0) | FD_CLOEXEC);
 
        int enable = 1;
        if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)) < 0) {
@@ -415,7 +526,7 @@ static int openPort(int port)
                return -1;
        }
 
-       if (listen(sock, 5) < 0) {
+       if (listen(sock, 1) < 0) {
                log_system_error("listen");
                close(sock);
                return -1;
@@ -425,7 +536,7 @@ static int openPort(int port)
                log_message("waiting for connection to port %d", port);
        }
        struct sockaddr_in caddr;
-       int csize = sizeof(caddr);
+       socklen_t csize = sizeof(caddr);
        int result = accept(sock, (struct sockaddr *)&caddr, &csize);
        if (result < 0) {
                log_system_error("accept");
@@ -436,9 +547,25 @@ static int openPort(int port)
                log_message("accepted connection to port %d", port);
        }
        close(sock);
+
+       // set read timeout for socket
+       struct timeval tv = { 0, 0 };
+       if (-1 == setsockopt(result, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof(tv))) {
+               log_system_error("setsockopt");
+               close(result);
+               return -1;
+       }
+
        return result;
 }
 
+
+/*  Functions check, that string `line' contains command `command' and if it is, function
+ *  parses up to two arguments of the command and assigns arguments to variables `*arg1'
+ *  and `*arg2'. If no arguments supplied, defaults values `def_arg1' and `def_arg2' might
+ *  be substituted instead.
+ *  Return value is -1 in case of parsing error or if `line' not matches specified `command'.
+ *  In case of success function returns 0. */
 static int is_command(char *line, char *command, int *arg1, int def_arg1, int *arg2, int def_arg2)
 {
        *arg1 = def_arg1;
@@ -465,6 +592,9 @@ static int is_command(char *line, char *command, int *arg1, int def_arg1, int *a
        return 0;
 }
 
+
+/* Function sends signal `signal' to PID `target_pid'. In case of error function
+ * reports error to the log. Return value is same as for kill(2) syscall. */
 static int send_signal(int target_pid, int signal)
 {
        if (target_pid == -1) {
@@ -483,31 +613,93 @@ static int send_signal(int target_pid, int signal)
        return result;
 }
 
+
+/* This function implements command loop: it reads lines from `control_fd' file or socket,
+ * tries to parse input line as commands and execute commands. Command loop is terminated
+ * in the following cases:
+ *
+ *   * EOF condition for `control_fd' file descriptor;
+ *   * main thread signals to stop other threads (via `stop_pipe');
+ *   * on reception of `exit' command.
+ *
+ * Following commands currently recognized (arguments in brackets are optional):
+ *
+ *   * "test [pid]" -- assign default process id (if no pid specified, using `cmd_pid'
+ *                     value -- PID of the process started by profctl;
+ *
+ *   * "exit [pid]" -- terminate specified process (or process with PID specified in "test" command)
+ *                     with SIGTERM signal and exit command loop;
+ *
+ *   * "stop [pid]" -- send signal 41 to specified (or default) process;
+ *
+ *   * "start [pid]" -- send signal 42 to specified (or default) process;
+ *
+ *   * "kill [pid [signal]]" -- send specified (or SIGINT by default) signal to
+ *                              specified (or default) process (on Tizen platform
+ *                              SIGINT assumes graceful termination of graphic
+ *                              applications).
+ *
+ * On result of execution of each command, the or the command is echoed back, or
+ * the one of the following messages send back:
+ *
+ *   * "!app_set_already (command...)" -- if "test" command received twice;
+ *   * "!kill_failed (command...) : -1" -- if "kill" command was unsuccessful.
+ */
 static void *command_loop_thread(void *arg)
 {
-       /* Read command loop */
-       ssize_t len;
-       char *line = malloc(len = 16);
-       while (!global_stop) {
-               if ((len = getline(&line, &len, ctrl_file_in)) == -1) {
-                       if (feof(ctrl_file_in)) {
-                               if (verbose) {
-                                       log_message("command stream eof");
-                               }
-                       }
-                       else {
-                               log_system_error("cannot read command");
-                       }
-                       global_stop = 1;
+       (void)arg;  // avoid "unused variable" warning
+
+       char line[2*getpagesize()];
+       char *wptr = line;
+
+       int const maxfd = (control_fd > stop_pipe[0] ? control_fd : stop_pipe[0]) + 1;
+       while (1) {
+               fd_set fdset;
+               FD_ZERO(&fdset);
+               FD_SET(control_fd, &fdset);
+               FD_SET(stop_pipe[0], &fdset);
+
+               int nr = select(maxfd, &fdset, NULL, NULL, NULL);
+               if (nr == -1) {
+                       if (errno == EINTR) continue;
+                       log_system_error("select");
+                       abort();
+               }
+
+               if (FD_ISSET(stop_pipe[0], &fdset))
+                       break;  // exit requested
+
+               // there is data to read?
+               if (! FD_ISSET(control_fd, &fdset)) continue;
+
+               // read next portion of data
+               ssize_t rs = read(control_fd, wptr, sizeof(line) - (wptr - line));
+               if (rs == -1) {
+                       log_system_error("read");
                        break;
                }
-               if (global_stop) {
+
+               if (rs == 0) {
+                       log_message("eof on control file");
                        break;
                }
+
+               // try to search end of line
+               char *lf = memchr(wptr, '\r', rs);
+               if (lf == NULL) lf = memchr(wptr, '\n', rs);
+               if (lf == NULL) {
+                       wptr += rs;
+                       continue;   // line isn't complete, need more data
+               }
+
+               // replace '\n' with `\0', compute command line length
+               *lf = 0;
+               size_t const len = lf - line;
+               size_t const tail = wptr + rs - lf - 1;
+               (void)len;  // avoid "unused variable" warning
+
+               // proces command line
                if (verbose) {
-                       if ((len > 0) && (line[len - 1] == '\n')) {
-                               line[len - 1] = 0;
-                       }
                        log_message("line: %s", line);
                }
                char *reply = line; // echo input line by default
@@ -527,7 +719,6 @@ static void *command_loop_thread(void *arg)
                                                stat_file_out = stdout;
                                        }
                                        proc_stat_thread_id = start_write_proc_stat(stat_file_out, pid, stat_period_usec, verbose);
-                                       proc_stat_thread_runs = 1;
                                }
                        }
                        else { // error
@@ -539,12 +730,12 @@ static void *command_loop_thread(void *arg)
                }
                else if (is_command(line, "exit", &pid, app_pid, NULL, 0) == 0) {
                        if (pid != -1) {
-                               send_signal(pid, SIGHUP);
+                               send_signal(pid, SIGTERM);
                        }
-                       if (verbose && !global_stop) {
-                               log_message("stop flag set (command_loop_thread)");
+                       if (verbose) {
+                               log_message("'exit' command given");
                        }
-                       global_stop = 1; // exit from the thread and then the program itself
+                       break;  // exit command loop
                }
                else if (is_command(line, "stop", &pid, app_pid, NULL, 0) == 0) {
                        send_signal(pid, SIGRTMIN + 7);
@@ -553,10 +744,18 @@ static void *command_loop_thread(void *arg)
                        send_signal(pid, SIGRTMIN + 8);
                }
                else {
+                       // Note, SIGINT have special meaning on Tizen platform: it allows
+                       // gracefully terminate (call all exit handlers, etc...) .NET application,
+                       // at same time SIGTERM terminates application in hard way and without
+                       // creation of the crashdump. SIGABRT also terminates application in hard
+                       // way, but also creates crashdump.
                        int arg2;
                        if (is_command(line, "kill", &pid, app_pid, &arg2, SIGINT) == 0) {
                                signal = arg2;
                        }
+                       else {
+                               log_error("unknown command '%s'", line);
+                       }
                }
                if (signal >= 0) {
                        int result = send_signal(pid, signal);
@@ -568,7 +767,7 @@ static void *command_loop_thread(void *arg)
                        }
                }
                if (ctrl_file_out != NULL) {
-                       if (fprintf(ctrl_file_out, "%s\n", reply) < 0 || fflush(ctrl_file_out) != 0) {
+                       if (fprintf(ctrl_file_out, "%s\r\n", reply) < 0 || fflush(ctrl_file_out) != 0) {
                                log_system_error("cannot write command reply");
                                fclose(ctrl_file_out);
                                ctrl_file_out = NULL;
@@ -577,16 +776,50 @@ static void *command_loop_thread(void *arg)
                if (verbose && reply != line) {
                        log_error(reply);
                }
-               free(buf);
-       } // while
-       free(line);
-       command_loop_thread_runs = 0;
+
+               // update write pointer, move tail to beginning of the buffer
+               wptr = line + tail;
+               memmove(line, lf+1, tail);
+       }
+
+       // signal all threads to exit
+       int unused = write(stop_pipe[1], "", 1);
+       (void)unused;  // avoid "output result not checked" and "unused variable" warnings
+
        if (verbose) {
                log_message("command loop finished");
        }
        return NULL;
 }
 
+
+/* This function handles SIGCHLD signal: on reception off such signal it
+ * sends byte to `chld_pipe' pipe, and program in main cycle should
+ * call waitpid(2) to get child's process return status. */
+static void sigchld_handler(int signo)
+{
+       (void)signo;  // avoid "unused variable" warning
+       int unused = write(chld_pipe[1], "", 1);
+       (void)unused;  // avoid "output result not checked" and "unused variable" warnings
+}
+
+
+/* Program execution starts here. Main function in general performs following
+ * actions:
+ *
+ *   1) parses command line arguments, opens files, sockets, starting child processes, etc...
+ *
+ *   2) starts one or two threads (one of which copies data from `pipe_fd' pipe to
+ *      data socket, second which prints process statistics on regular intervals);
+ *
+ *   3) starts monitoring of `cmd_pid' process (one which launched previously at step 1),
+ *      and when it is finished -- stop parallel threads, kill other child processes
+ *      and terminate. In case, when running with "heaptrack", in main cycle also
+ *      "helper processs" is monitored and cycle is break then helper process terminates.
+ *      This is needed, because in absence of helper process, the profiled process might
+ *      be forever blocked on writing to fifo with no readers (if helper process was
+ *      terminated before the moment, when profiling process opens the fifo).
+ */
 int main(int argc, char **argv)
 {
        time_t start_time = time(NULL);
@@ -595,6 +828,26 @@ int main(int argc, char **argv)
                return 1;
        }
 
+       // open `stop_pipe' and `chld_pipe', set FD_CLOEXEC flag for both
+       {
+         if (pipe(stop_pipe) == -1) {
+                 log_system_error_and_exit("pipe");
+         }
+
+         int i;
+         for (i = 0; i < 2; i++)
+                 fcntl(stop_pipe[i], F_SETFD, fcntl(stop_pipe[i], F_GETFD, 0) | FD_CLOEXEC);
+
+         if (pipe(chld_pipe) == -1) {
+                 log_system_error_and_exit("pipe");
+         }
+
+         for (i = 0; i < 2; i++)
+                 fcntl(chld_pipe[i], F_SETFD, fcntl(chld_pipe[i], F_GETFD, 0) | FD_CLOEXEC);
+       }
+
+       signal(SIGCHLD, sigchld_handler);
+
        log_pid = 1;
        log_tid = 1;
 
@@ -610,12 +863,11 @@ int main(int argc, char **argv)
 
        FILE *log_file = fopen(oname, "w");
        if (log_file == NULL) {
-               log_system_error_and_exit("freopen");
+               log_system_error_and_exit("fopen '%s'", oname);
        }
        setlinebuf(log_file);
 
        log_set_file(log_file);
-       fclose(stderr); // NOTE: detaching from stdin/out/err streams
 
        if (verbose) {
                fprintf(log_file, "=== Started (pid: %d) ===\n", getpid());
@@ -649,73 +901,13 @@ int main(int argc, char **argv)
                }
        }
 
-#if TIZEN
-       struct termios term;
-       if (controlPort < 0 && dataPort < 0 && statPort < 0) {
-               /* stty */
-               tcgetattr(0, &term);
-               sterm = term;
-
-               term.c_lflag &= ~(ECHO|ECHONL); /* no echo and so on */
-               term.c_oflag &= ~OPOST; /* no additional CR and so on */
-
-               tcsetattr(0, TCSANOW, &term);
-       }
-#endif /* TIZEN */
-
-       if (controlPort > 0)
-       {
-               fclose(stdin); // NOTE: detaching from stdin/out/err streams
-       }
-       if (dataPort > 0 && statPort > 0) {
-               fclose(stdout); // NOTE: detaching from stdin/out/err streams
-       }
-
-       if (cmd_idx != -1) {
-               // TODO: should be refactored: use separated launcher
-
-               if (verbose) {
-                       log_message("launch command process %s", argv[cmd_idx]);
-                       for (int i = 0; i < additional_env_index; i++) {
-                               log_message("  additional environment: %s", additional_env[i]);
-                       }
-               }
-
-               cmd_pid = fork();
-               if (!cmd_pid) {
-                       /* Child branch */
-
-                       // TODO: close unneeded descriptors
-                       // NOTE: /proc/self/fd/ can be used to iterate open descriptors
-                       close(STDIN_FILENO);
-                       close(STDOUT_FILENO);
-                       close(STDERR_FILENO);
-
-                       for (int i = 0; i < additional_env_index; i++) {
-                               putenv(additional_env[i]);
-                       }
-                       execv(argv[cmd_idx], &argv[cmd_idx]);
-                       exit(1);
-               } else if (cmd_pid == -1) {
-                       log_system_error_and_exit("cannot fork command process");
-               } else {
-                       if (verbose) {
-                               log_message("waiting for command process exit (pid=%d)", cmd_pid);
-                       }
-               }
-       }
-
        if (controlPort > 0) {
-               control_socket = openPort(controlPort);
-               if (control_socket < 0) {
+               control_fd = openPort(controlPort);
+               if (control_fd < 0) {
                        log_error("cannot open control port");
                        exit(1);
                }
-               ctrl_file_in = fdopen(control_socket, "r");
-               if (ctrl_file_in == NULL) {
-                       log_system_error_and_exit("fdopen(control, r)");
-               }
-               ctrl_file_out = fdopen(control_socket, "w");
+               ctrl_file_out = fdopen(control_fd, "w");
                if (ctrl_file_out == NULL) {
                        log_system_error_and_exit("fdopen(control, w)");
                }
@@ -724,36 +916,17 @@ int main(int argc, char **argv)
                }
        }
        else {
-               ctrl_file_in = stdin;
+               ctrl_file_out = stdout;
+               control_fd = fileno(stdin);
        }
 
+       fcntl(control_fd, F_SETFL, fcntl(control_fd, F_GETFL, 0) | O_NONBLOCK); /* make input non blocking */
+
        if (pname) {
                // TODO!! log to stderr as well if failed to start data_output_thread (i.e. before all 'return 1')
                if (openFileProcess() != 0) {
                        return 1;
                }
-
-               if (dataPort > 0) {
-                       // data_output_thread will open the data port
-/*!!
-                       data_socket = openPort(dataPort);
-                       if (data_socket < 0) {
-                               return 1;
-                       }
-                       data_file_out = fdopen(data_socket, "w");
-                       if (data_file_out == NULL) {
-                               log_system_error("fdopen(data,w)");
-                               return 1;
-                       }
-                       if (fprintf(data_file_out, "ready\n") < 0 || fflush(data_file_out) != 0) {
-                               log_system_error("cannot write 'ready' prompt to data stream");
-                               return 1;
-                       }
-*/
-               }
-               else {
-                       data_file_out = stdout;
-               }
        }
 
        if (doinfo && (statPort > 0)) {
@@ -772,45 +945,85 @@ int main(int argc, char **argv)
                }
        }
 
+       // Start profiled process AFTER opening the sockets
+       // to avoid the need to kill the process if sockets can't be opened.
+       if (cmd_idx != -1) {
+
+               if (verbose) {
+                       log_message("launch command process %s", argv[cmd_idx]);
+                       for (int i = 0; i < additional_env_index; i++) {
+                               log_message("  additional environment: %s", additional_env[i]);
+                       }
+               }
+
+               cmd_pid = fork();
+               if (!cmd_pid) {
+                       /* Child branch: */
+                       /* redirect stdin to "/dev/null" */
+                       int in = open("/dev/null", O_RDONLY);
+                       if (-1 == in || -1 == dup2(in, STDIN_FILENO)) close(STDIN_FILENO);
+
+                       /* redirect stdout to "/dev/null" */
+                       int out = open("/dev/null", O_WRONLY);
+                       if (-1 == out || -1 == dup2(out, STDOUT_FILENO)) close(STDOUT_FILENO);
+
+                       /* redirect stderr to log (or left unchanged, if not possible to redirect) */
+                       int fd = get_log_fd();
+                       if (fd >= 0) dup2(fd, STDERR_FILENO);
+
+                       for (int i = 0; i < additional_env_index; i++) {
+                               putenv(additional_env[i]);
+                       }
+                       execv(argv[cmd_idx], &argv[cmd_idx]);
+                       log_system_error("can't exec %s", argv[cmd_idx]);
+                       exit(EXIT_FAILURE);
+
+               } else if (cmd_pid == -1) {
+                       log_system_error_and_exit("cannot fork command process");
+               } else {
+                       if (verbose) {
+                               log_message("waiting for command process exit (pid=%d)", cmd_pid);
+                       }
+               }
+       }
+
        pthread_t data_output_thread_id;
+       memset(&data_output_thread_id, 0, sizeof(data_output_thread_id));
        if (pname) {
-               data_output_thread_runs = 1;
                data_output_thread_id = SimpleThread(&data_output_thread, NULL);
        }
 
-       command_loop_thread_runs = 1;
        pthread_t command_loop_thread_id = SimpleThread(&command_loop_thread, NULL);
 
-       struct timespec poll_timeout;
-       poll_timeout.tv_sec = 0;
-       poll_timeout.tv_nsec = 250 * 1000000L; // 250 msec
-
+       int status = -1;
        int last_app_pid = -1;
-       while (!global_stop) {
-               if (command_loop_thread_runs &&
-                       pthread_timedjoin_np(command_loop_thread_id, NULL, &poll_timeout) == 0)
-               {
-                       break; // command loop thread finished
-               }
-               if (global_stop) {
-                       break;
-               }
-               if (data_output_thread_runs &&
-                       pthread_timedjoin_np(data_output_thread_id, NULL, &poll_timeout) == 0)
-               {
-                       break; // data output thread finished
-               }
-               if (global_stop) {
-                       break;
+       int const maxfd = (chld_pipe[0] > stop_pipe[0] ? chld_pipe[0] : stop_pipe[0]) + 1;
+
+       // This is main loop, in which program waits until launched process terminates or
+       // until other thread not requests program termination (via writing to `stop_pipe[1]')
+       while (1) {
+               fd_set fdset;
+               FD_ZERO(&fdset);
+               FD_SET(stop_pipe[0], &fdset);
+               FD_SET(chld_pipe[0], &fdset);
+               struct timeval tv = { 0, 100000 }; // 100 msec
+               int nr = select(maxfd, &fdset, NULL, NULL, &tv);
+               if (nr == -1) {
+                       if (errno == EINTR) continue;
+                       log_system_error("select");
+                       abort();
                }
+
+               if (FD_ISSET(stop_pipe[0], &fdset))
+                       break;  // stop request
+
                if (app_pid != -1) {
                        int app_runs = (kill(app_pid, 0) == 0);
                        if (!app_runs) {
                                if (verbose) {
                                        log_message("controlled application finished (pid=%d)", app_pid);
                                }
-                               global_stop = 1;
-                               break;
+                               break;  // stop wairing
                        }
                        if (last_app_pid == -1) {
                                last_app_pid = app_pid;
@@ -819,84 +1032,149 @@ int main(int argc, char **argv)
                                }
                        }
                }
-               // TODO: should separated launcher to be used for pid control?
-               if (cmd_pid != -1) {
-                       if (waitpid(cmd_pid, NULL, WNOHANG) > 0) {
-                               if (verbose) {
-                                       log_message("command process finished (pid=%d)", cmd_pid);
+
+               // check if launched process finished, get it's return code
+               if (cmd_pid != -1 && FD_ISSET(chld_pipe[0], &fdset)) {
+                       if (waitpid(cmd_pid, &status, WNOHANG) > 0) {
+                               // use shell-compatible exit status as described here:
+                               // https://www.tldp.org/LDP/abs/html/exitcodes.html
+                               status = WIFSIGNALED(status) ? 128 + WTERMSIG(status) : WEXITSTATUS(status);
+                               if (status != 0) {
+                                       log_error("command process finished (pid=%d) with error %d", cmd_pid, status);
+                               }
+                               else if (verbose) {
+                                       log_message("command process finished (pid=%d) with 0 exit status", cmd_pid);
                                }
                                cmd_pid = -1;
-                               global_stop = 1;
-                               break;
+                               break;  // stop waiting
                        }
                }
-               // sleep to reduce CPU usage
-               usleep(100 * 1000); // 100 msec
-       }
-       if (verbose && !global_stop) {
-               log_message("stop flag set (main)");
-       }
-       global_stop = 1;
-       struct timespec join_timeout;
-       join_timeout.tv_sec = 0;
-       join_timeout.tv_nsec = 500 * 1000000L; // 500 msec
-       if (command_loop_thread_runs) {
-               if (pthread_timedjoin_np(command_loop_thread_id, NULL, &join_timeout) != 0) {
-                       pthread_cancel(command_loop_thread_id);
-               }
-       }
-       if (data_output_thread_runs) {
-               if (pthread_timedjoin_np(data_output_thread_id, NULL, &join_timeout) != 0) {
-                       pthread_cancel(data_output_thread_id);
-               }
-       }
-       if (proc_stat_thread_runs) {
-               if (pthread_timedjoin_np(proc_stat_thread_id, NULL, &join_timeout) != 0) {
-                       pthread_cancel(proc_stat_thread_id);
+
+               if (exe_pid > 0) {
+                       int code;
+                       if (waitpid(exe_pid, &code, WNOHANG) > 0) {
+                               code = WIFSIGNALED(code) ? 128 + WTERMSIG(code) : WEXITSTATUS(code);
+                               if (code != 0) {
+                                       log_error("helper process finished (pid=%d) with error %d", exe_pid, code);
+                               }
+                               else {
+                                       log_message("helper process finished (pid=%d) with 0 exit status", exe_pid);
+                               }
+                               exe_pid = -1;
+                               break;  // terminate execution
+                       }
                }
        }
 
-       // TODO: should be refactored: move to separated launcher
+       // terminate helper process, if it still not terminated
+       if (exe_pid > 0) kill(exe_pid, 9);
+
+       // stop other threads
+       int unused = write(stop_pipe[1], "", 1);
+       (void)unused;  // avoid "output result not checked" and "unused variable" warnings
+
+       pthread_join(command_loop_thread_id, NULL);
+       if (pname) pthread_join(data_output_thread_id, NULL);
+       if (app_pid != -1) pthread_join(proc_stat_thread_id, NULL);
+
        if (cmd_pid != -1) {
-               int result = 0;
-               for (int retry = 0; retry < 10; retry++) {
-                       result = waitpid(cmd_pid, NULL, WNOHANG);
-                       if (result != 0) {
-                               break;
+
+               // stop launched process forcibly, if it still not stopped
+               do {
+                       // give 1 sec for application to terminate
+                       fd_set fdset;
+                       struct timeval tv = { 1, 0 }; // 1 sec
+                       while (tv.tv_sec > 0 || tv.tv_usec > 0) {
+                               FD_ZERO(&fdset);
+                               FD_SET(chld_pipe[0], &fdset);
+
+                               int nr = select(chld_pipe[0] + 1, &fdset, NULL, NULL, &tv);
+                               if (nr == -1) {
+                                       if (errno == EINTR) continue;
+                                       log_system_error("select");
+                                       abort();
+                               }
+
+                               int res = waitpid(cmd_pid, &status, WNOHANG);
+                               if (res < 0) {
+                                       log_system_error("waitpid(%d)", cmd_pid);
+                                       cmd_pid = -1;
+                                       break;
+                               }
+                               if (res == cmd_pid) {
+                                       cmd_pid = -1;
+                                       break;
+                               }
                        }
-                       usleep(100 * 1000); // 100 ms
-               }
-               if (result == 0) {
+
+                       if (cmd_pid == -1) break;
+
+                       // send SIGTERM and give additional 1 sec
                        if (verbose) {
                                log_message("kill command process (pid=%d)", cmd_pid);
                        }
-                       result = send_signal(cmd_pid, SIGINT);
-                       if (result == 0)
-                       {
-                               for (int retry = 0; retry < 10; retry++) {
-                                       result = waitpid(cmd_pid, NULL, WNOHANG);
-                                       if (result != 0) {
-                                               break;
-                                       }
-                                       usleep(100 * 1000); // 100 ms
+
+                       if (send_signal(cmd_pid, SIGTERM) != 0) {
+                               cmd_pid = -1;
+                               break;
+                       }
+
+                       tv = (struct timeval){ 1, 0 };
+                       while (tv.tv_sec > 0 || tv.tv_usec > 0) {
+                               FD_ZERO(&fdset);
+                               FD_SET(chld_pipe[0], &fdset);
+
+                               int nr = select(chld_pipe[0] + 1, &fdset, NULL, NULL, &tv);
+                               if (nr == -1) {
+                                       if (errno == EINTR) continue;
+                                       log_system_error("select");
+                                       abort();
                                }
-                               if (result == 0) {
-                                       result = send_signal(cmd_pid, SIGKILL);
-                                       if (result == 0)
-                                       {
-                                               result = waitpid(cmd_pid, NULL, 0);
-                                       }
+
+                               int res = waitpid(cmd_pid, &status, WNOHANG);
+                               if (res < 0) {
+                                       log_system_error("waitpid(%d)", cmd_pid);
+                                       cmd_pid = -1;
+                                       break;
+                               }
+                               if (res == cmd_pid) {
+                                       cmd_pid = -1;
+                                       break;
                                }
                        }
-               }
-               if (result > 0) {
+
+                       if (cmd_pid == -1) break;
+
+                       // kill with SIGKILL
+                       if (send_signal(cmd_pid, SIGKILL) != 0) {
+                               cmd_pid = -1;
+                               break;
+                       }
+
+                       if (waitpid(cmd_pid, &status, 0) < 0) {
+                               log_system_error("waitpid(%d)", cmd_pid);
+                       }
+
+               } while(0);
+
+               if (status != -1) {
+                       status = WIFSIGNALED(status) ? 128 + WTERMSIG(status) : WEXITSTATUS(status);
                        if (verbose) {
-                               log_message("command process finished (pid=%d)", cmd_pid);
+                               log_message("command process finished (pid=%d) with code %d", cmd_pid, status);
                        }
-                       cmd_pid = -1;
-               } else if (result == -1) {
-                       log_system_error("cannot finish command process (pid=%d)", cmd_pid);
                }
+               else {
+                       log_system_error("cannot finish command process!");
+                       exit(EXIT_FAILURE);
+               }
+       }
+
+       /* print "!exit <error-code>" message to `control_fd' -- this message allows to know
+        * exit code of the profiled process to the process, which controls whole workflow
+        * (usually Visual Studio) */
+       if (ctrl_file_out != NULL) {
+               if (fprintf(ctrl_file_out, "!exit %d\r\n", status) < 0 || fflush(ctrl_file_out) != 0)
+                       log_system_error("cannot write command reply");
        }
 
        return 0;