public function exec($exec, $args = [])
    {
        if (!$this->sseInProgress) {
            $this->set(function () use ($exec, $args) {
                $a = $args ? (' with '.count($args).' arguments') : '';
                $this->output('--[ Executing '.$exec.$a.' ]--------------');

                $this->exec($exec, $args);

                $this->output('--[ Exit code: '.$this->last_exit_code.' ]------------');
            });

            return;
        }

        list($proc, $pipes) = $this->execRaw($exec, $args);

        stream_set_blocking($pipes[1], false);
        stream_set_blocking($pipes[2], false);
        // $pipes contain streams that are still open and not EOF
        while ($pipes) {
            $read = $pipes;
            $j1 = $j2 = null;
            if (stream_select($read, $j1, $j2, 2) === false) {
                throw new Exception(['stream_select() returned false.']);
            }

            $stat = proc_get_status($proc);
            if (!$stat['running']) {
                proc_close($proc);
                break;
            }

            foreach ($read as $f) {
                $data = fgets($f);
                $data = rtrim($data);
                if (!$data) {
                    continue;
                }

                if ($f === $pipes[2]) {
                    // STDERR
                    $this->warning($data);
                } else {
                    // STDOUT
                    $this->output($data);
                }
            }
        }

        $this->last_exit_code = $stat['exitcode'];

        return $this->last_exit_code ? false : $this;
    }