Remove socket prewarming

The potential for breakage is too high, and I am working on an
alternative solution that will be better long term.

Prewarming is still used for kittens launched via keybindings
This commit is contained in:
Kovid Goyal 2022-08-20 13:38:33 +05:30
parent 4cc0138a28
commit 44ccdd36d6
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
11 changed files with 13 additions and 1278 deletions

View File

@ -23,10 +23,6 @@ following dependencies are installed first.
should use a version of the source that matches the binary version as closely
as possible, since the two are tightly coupled.
You may need to unset the :envvar:`KITTY_PREWARM_SOCKET` environment variable,
otherwise your modified code will not be executed, when running some of the
``kitty +`` and ``kitty @`` commands.
Dependencies
----------------

View File

@ -40,7 +40,7 @@ Detailed list of changes
- A new option :opt:`remote_control_password` to use fine grained permissions for what can be remote controlled (:disc:`5320`)
- Reduce startup latency by ~30 milliseconds when running kittens and remote control commands inside kitty (:iss:`5159`)
- Reduce startup latency by ~30 milliseconds when running kittens via key bindings inside kitty (:iss:`5159`)
- A new option :opt:`modify_font` to adjust various font metrics like underlines, cell sizes etc. (:pull:`5265`)

View File

@ -142,17 +142,6 @@ Variables that kitty sets when running child programs
A public key that programs can use to communicate securely with kitty using
the remote control protocol. The format is: :code:`protocol:key data`.
.. envvar:: KITTY_PREWARM_SOCKET
Path to a UNIX domain socket used to avoid Python interpreter startup
latency when running kittens, or remote control or using ``kitty +launch`` or
``kitty +runpy``.
.. envvar:: KITTY_PREWARM_SOCKET_REAL_TTY
Path to the the PTY used to run the prewarmed process in when using
:envvar:`KITTY_PREWARM_SOCKET`.
.. envvar:: WINDOWID
The id for the :term:`OS Window <os_window>` the program is running in. Only available

View File

@ -2,9 +2,7 @@
# License: GPLv3 Copyright: 2021, Kovid Goyal <kovid at kovidgoyal.net>
import os
import re
import sys
from typing import List
from kitty.conf.generate import write_output
@ -24,9 +22,6 @@ def patch_color_list(path: str, colors: List[str], name: str, spc: str = ' ')
def main() -> None:
if 'prewarmed' in getattr(sys, 'kitty_run_data'):
os.environ.pop('KITTY_PREWARM_SOCKET')
os.execlp(sys.executable, sys.executable, '+launch', __file__, *sys.argv[1:])
from kitty.options.definition import definition
write_output('kitty', definition)
nullable_colors = []

View File

@ -148,7 +148,6 @@ def process_env() -> Dict[str, str]:
ssl_env_var = getattr(sys, 'kitty_ssl_env_var', None)
if ssl_env_var is not None:
ans.pop(ssl_env_var, None)
ans.pop('KITTY_PREWARM_SOCKET', None)
return ans
@ -256,9 +255,6 @@ class Child:
env['KITTY_LISTEN_ON'] = boss.listening_on
else:
env.pop('KITTY_LISTEN_ON', None)
if not self.is_prewarmed:
env['KITTY_PREWARM_SOCKET'] = boss.prewarm.socket_env_var()
env['KITTY_PREWARM_SOCKET_REAL_TTY'] = ' ' * 32
if self.cwd:
# needed in case cwd is a symlink, in which case shells
# can use it to display the current directory name rather
@ -464,11 +460,6 @@ class Child:
import termios
if self.child_fd is None:
return False
tty_name = self.foreground_environ.get('KITTY_PREWARM_SOCKET_REAL_TTY')
if tty_name and tty_name.startswith('/'):
with open(os.open(tty_name, os.O_RDWR | os.O_CLOEXEC | os.O_NOCTTY, 0)) as real_tty:
t = termios.tcgetattr(real_tty.fileno())
else:
t = termios.tcgetattr(self.child_fd)
if not t[3] & termios.ISIG:
return False

View File

@ -268,11 +268,9 @@ read_exe_path(char *exe, size_t buf_sz) {
}
#endif // }}}
extern void use_prewarmed_process(int argc, char *argv[], char *envp[]);
int main(int argc, char *argv[], char* envp[]) {
if (argc < 1 || !argv) { fprintf(stderr, "Invalid argc/argv\n"); return 1; }
use_prewarmed_process(argc, argv, envp);
char exe[PATH_MAX+1] = {0};
char exe_dir_buf[PATH_MAX+1] = {0};
FREE_AFTER_FUNCTION const char *lc_ctype = NULL;

View File

@ -1,827 +0,0 @@
/*
* Copyright (C) 2022 Kovid Goyal <kovid at kovidgoyal.net>
*
* Distributed under terms of the GPL3 license.
*/
// for SA_RESTART
#define _XOPEN_SOURCE 700
// for cfmakeraw
#ifdef __APPLE__
#define _DARWIN_C_SOURCE 1
const static int has_splice = 0;
#else
#define _DEFAULT_SOURCE
// for splice()
#define _GNU_SOURCE
#ifdef __linux__
#define HAS_SPLICE
const static int has_splice = 0; // sadly the kernel doesnt support splice between tty fds anymore
// https://www.spinics.net/lists/kernel/msg3799785.html
#else
const static int has_splice = 0;
#endif
#endif
// Includes {{{
#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>
#include <termios.h>
#include <errno.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <poll.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#ifdef __APPLE__
#include <util.h>
#include <mach-o/dyld.h>
#include <sys/syslimits.h>
#include <sys/stat.h>
#else
#include <pty.h>
#endif
#include <limits.h>
#define arraysz(x) (sizeof(x)/sizeof(x[0]))
#define MAX(x, y) __extension__ ({ \
__typeof__ (x) a = (x); __typeof__ (y) b = (y); \
a > b ? a : b;})
#define MIN(x, y) __extension__ ({ \
__typeof__ (x) a = (x); __typeof__ (y) b = (y); \
a < b ? a : b;})
// }}}
#define IO_BUZ_SZ 8192
#define remove_i_from_array(array, i, count) { \
(count)--; \
if ((i) < (count)) { \
memmove((array) + (i), (array) + (i) + 1, sizeof((array)[0]) * ((count) - (i))); \
}}
// ancient macs (10.15) have no MSG_NOSIGNAL
#ifndef MSG_NOSIGNAL
# define MSG_NOSIGNAL 0
# ifdef SO_NOSIGPIPE
# define KITTY_USE_SO_NOSIGPIPE
# else
# error "Cannot block SIGPIPE!"
# endif
#endif
typedef struct transfer_buf {
char *buf;
size_t sz;
} transfer_buf;
static transfer_buf from_child_tty = {0};
static transfer_buf to_child_tty = {0};
static char child_tty_name[256];
static int child_master_fd = -1, child_slave_fd = -1;
static struct winsize self_winsize = {0};
static struct termios self_termios = {0}, restore_termios = {0};
static bool termios_needs_restore = false;
static int self_ttyfd = -1, socket_fd = -1, signal_read_fd = -1, signal_write_fd = -1;
static int stdin_pos = -1, stdout_pos = -1, stderr_pos = -1;
static char fd_send_buf[256];
struct iovec launch_msg = {0};
struct msghdr launch_msg_container = {.msg_control = fd_send_buf, .msg_controllen = sizeof(fd_send_buf), .msg_iov = &launch_msg, .msg_iovlen = 1 };
static size_t launch_msg_cap = 0;
char *launch_msg_buf = NULL;
static pid_t child_pid = 0;
static void
left_shift_buffer(transfer_buf *t, size_t n) {
if (t->sz > n) {
t->sz -= n;
memmove(t->buf, t->buf + n, t->sz);
} else t->sz = 0;
}
#define err_prefix "prewarm wrapper process error: "
static inline void
print_error(const char *s, int errnum) {
if (errnum != 0) fprintf(stderr, "%s%s: %s\n\r", err_prefix, s, strerror(errnum));
else fprintf(stderr, "%s%s\n\r", err_prefix, s);
}
#define pe(fmt, ...) { fprintf(stderr, err_prefix); fprintf(stderr, fmt, __VA_ARGS__); fprintf(stderr, "\n\r"); }
static bool
parse_long(const char *str, long *val) {
char *temp;
bool rc = true;
errno = 0;
const long t = strtol(str, &temp, 0);
if (temp == str || *temp != '\0' || ((*val == LONG_MIN || *val == LONG_MAX) && errno == ERANGE)) rc = false;
*val = t;
return rc;
}
static inline int
safe_open(const char *path, int flags, mode_t mode) {
while (true) {
int fd = open(path, flags, mode);
if (fd == -1 && errno == EINTR) continue;
return fd;
}
}
static inline void
safe_close(int fd) {
while(close(fd) != 0 && errno == EINTR);
}
static inline int
safe_dup2(int a, int b) {
int ret;
while((ret = dup2(a, b)) < 0 && errno == EINTR);
return ret;
}
static inline bool
safe_tcsetattr(int fd, int actions, const struct termios *tp) {
int ret = 0;
while((ret = tcsetattr(fd, actions, tp)) != 0 && errno == EINTR);
return ret == 0;
}
static ssize_t
safe_read(int fd, void *buf, size_t n) {
ssize_t ret = 0;
while((ret = read(fd, buf, n)) ==-1 && errno == EINTR);
return ret;
}
static ssize_t
safe_send(int fd, void *buf, size_t n, int flags) {
ssize_t ret = 0;
while((ret = send(fd, buf, n, flags)) ==-1 && errno == EINTR);
return ret;
}
static ssize_t
safe_write(int fd, void *buf, size_t n) {
ssize_t ret = 0;
while((ret = write(fd, buf, n)) ==-1 && errno == EINTR);
return ret;
}
static bool
set_blocking(int fd, bool blocking) {
if (fd < 0) return false;
int flags = fcntl(fd, F_GETFL, 0);
if (flags == -1) return false;
flags = blocking ? (flags & ~O_NONBLOCK) : (flags | O_NONBLOCK);
return (fcntl(fd, F_SETFL, flags) == 0) ? true : false;
}
static int
connect_to_socket_synchronously(const char *addr) {
struct sockaddr_un sock_addr = {.sun_family=AF_UNIX};
strncpy(sock_addr.sun_path, addr, sizeof(sock_addr.sun_path) - 1);
int fd = socket(AF_UNIX, SOCK_STREAM, 0);
#ifdef __linux__
const size_t addrlen = strnlen(sock_addr.sun_path, sizeof(sock_addr.sun_path)) + sizeof(sock_addr.sun_family);
if (sock_addr.sun_path[0] == '@') sock_addr.sun_path[0] = 0;
#else
const size_t addrlen = sizeof(sock_addr);
#endif
if (connect(fd, (struct sockaddr*)&sock_addr, addrlen) != 0) {
if (errno != EINTR && errno != EINPROGRESS) return -1;
struct pollfd poll_data = {.fd=fd, .events=POLLOUT};
while (poll (&poll_data, 1, -1) == -1) { if (errno != EINTR) return -1; }
int socket_error_code = 0;
socklen_t sizeof_socket_error_code = sizeof(socket_error_code);
if (getsockopt (fd, SOL_SOCKET, SO_ERROR, &socket_error_code, &sizeof_socket_error_code) == -1) return -1;
if (socket_error_code != 0) return -1;
}
return fd;
}
static bool
is_prewarmable(int argc, char *argv[]) {
if (argc < 2) return false;
switch (argv[1][0]) {
case '+':
if (argv[1][1] != 0) return strcmp(argv[1], "+open") != 0;
if (argc < 3) return false;
return strcmp(argv[2], "open") != 0;
break;
case '@':
return true;
break;
default:
return false;
break;
}
return false;
}
static bool
get_termios_state(struct termios *t) {
while (tcgetattr(self_ttyfd, t) != 0) {
if (errno != EINTR) return false;
}
return true;
}
static void
restore_termios_settings(void) {
if (self_ttyfd > -1 && termios_needs_restore) {
// we only restore termios if its current state is the same as the result of cfmakeraw() on the startup termios state
// this is to try to detect if something like less changed the termios state. This allows, for instance kitty @ ls | less to work
struct termios current_state;
if (get_termios_state(&current_state) && memcmp(&self_termios, &current_state, sizeof(current_state)) == 0) {
safe_tcsetattr(self_ttyfd, TCSAFLUSH, &restore_termios);
termios_needs_restore = false;
}
}
}
static void
reapply_termios_settings(void) {
safe_tcsetattr(self_ttyfd, TCSANOW, &self_termios);
termios_needs_restore = true;
}
static void
cleanup(void) {
child_pid = 0;
restore_termios_settings();
#define cfd(fd) if (fd > -1) { safe_close(fd); fd = -1; }
cfd(child_master_fd); cfd(child_slave_fd);
cfd(self_ttyfd); cfd(socket_fd); cfd(signal_read_fd); cfd(signal_write_fd);
#undef cfd
if (launch_msg_buf) { free(launch_msg_buf); launch_msg.iov_len = 0; launch_msg_buf = NULL; }
if (from_child_tty.buf) { free(from_child_tty.buf); from_child_tty.buf = NULL; }
}
static bool
safe_winsz(int fd, int action, struct winsize *ws) {
int ret;
while ((ret = ioctl(fd, action, ws)) == -1 && errno == EINTR);
return ret != -1;
}
static bool
get_window_size(void) {
return safe_winsz(self_ttyfd, TIOCGWINSZ, &self_winsize);
}
bool
set_iutf8(int fd, bool on) {
(void)fd; (void)on;
#ifdef IUTF8
struct termios attrs;
if (tcgetattr(fd, &attrs) != 0) return false;
if (on) attrs.c_iflag |= IUTF8;
else attrs.c_iflag &= ~IUTF8;
if (tcsetattr(fd, TCSANOW, &attrs) != 0) return false;
#endif
return true;
}
static bool
open_pty(void) {
while (openpty(&child_master_fd, &child_slave_fd, child_tty_name, &self_termios, &self_winsize) == -1) {
if (errno != EINTR) return false;
}
return set_iutf8(child_master_fd, true);
}
static void
handle_signal(int sig_num, siginfo_t *si, void *ucontext) {
(void)sig_num; (void)ucontext;
int save_err = errno;
char *buf = (char*)si;
size_t sz = sizeof(siginfo_t);
while (signal_write_fd != -1 && sz) {
// as long as sz is less than PIPE_BUF write will either write all or return -1 with EAGAIN
// so we are guaranteed atomic writes, barring implementation bugs
ssize_t ret = safe_write(signal_write_fd, buf, sz);
if (ret <= 0) break;
sz -= ret;
buf += ret;
}
errno = save_err;
}
static bool
setup_signal_handler(void) {
int fds[2];
if (pipe(fds) != 0) return false;
signal_read_fd = fds[0]; signal_write_fd = fds[1];
set_blocking(signal_write_fd, false);
sigset_t masked_signals;
sigemptyset(&masked_signals);
sigaddset(&masked_signals, SIGWINCH);
sigaddset(&masked_signals, SIGINT);
sigaddset(&masked_signals, SIGTERM);
sigaddset(&masked_signals, SIGQUIT);
sigaddset(&masked_signals, SIGHUP);
sigaddset(&masked_signals, SIGTSTP);
struct sigaction act = {.sa_sigaction=handle_signal, .sa_flags=SA_SIGINFO | SA_RESTART, .sa_mask = masked_signals};
#define a(which) if (sigaction(which, &act, NULL) != 0) return false;
a(SIGWINCH); a(SIGINT); a(SIGTERM); a(SIGQUIT); a(SIGHUP); a(SIGTSTP);
#undef a
return true;
}
static void
setup_stdio_handles(void) {
int pos = 0;
if (!isatty(STDIN_FILENO)) stdin_pos = pos++;
if (!isatty(STDOUT_FILENO)) {
stdout_pos = pos++;
}
if (!isatty(STDERR_FILENO)) stderr_pos = pos++;
}
static bool
ensure_launch_msg_space(size_t sz) {
if (launch_msg_cap > launch_msg.iov_len + sz) return true;
const size_t c = MAX(2 * launch_msg_cap, launch_msg_cap + launch_msg.iov_len + sz + 8);
launch_msg_cap = MAX(c, 64 * 1024);
launch_msg_buf = realloc(launch_msg_buf, launch_msg_cap);
return launch_msg_buf != NULL;
}
static bool
write_item_to_launch_msg(const char *prefix, const char *data) {
size_t prefixlen = strlen(prefix), datalen = strlen(data), msg_len = 8 + prefixlen + datalen;
if (!ensure_launch_msg_space(msg_len)) return false;
memcpy(launch_msg_buf + launch_msg.iov_len, prefix, prefixlen);
launch_msg.iov_len += prefixlen;
launch_msg_buf[launch_msg.iov_len++] = ':';
memcpy(launch_msg_buf + launch_msg.iov_len, data, datalen);
launch_msg.iov_len += datalen;
launch_msg_buf[launch_msg.iov_len++] = 0;
launch_msg.iov_base = launch_msg_buf;
return true;
}
extern char **environ;
static bool
create_launch_msg(int argc, char *argv[]) {
#define w(prefix, data) { if (!write_item_to_launch_msg(prefix, data)) return false; }
static char buf[4*PATH_MAX];
w("tty_name", child_tty_name);
snprintf(buf, sizeof(buf), "%zu", sizeof(self_winsize));
w("winsize", buf);
if (getcwd(buf, sizeof(buf))) { w("cwd", buf); }
for (int i = 0; i < argc; i++) w("argv", argv[i]);
char **s = environ;
for (; *s; s++) w("env", *s);
int num_fds = 0, fds[4];
#define sio(which, x) if (which##_pos > -1) { snprintf(buf, sizeof(buf), "%d", which##_pos); w(#which, buf); fds[num_fds++] = x; }
sio(stdin, STDIN_FILENO); sio(stdout, STDOUT_FILENO); sio(stderr, STDERR_FILENO);
#undef sio
w("finish", "");
struct cmsghdr *cmsg = CMSG_FIRSTHDR(&launch_msg_container);
cmsg->cmsg_len = CMSG_LEN(sizeof(fds[0]) * num_fds);
memcpy(CMSG_DATA(cmsg), fds, num_fds * sizeof(fds[0]));
launch_msg_container.msg_controllen = cmsg->cmsg_len;
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_RIGHTS;
return true;
#undef w
}
static int exit_status = EXIT_FAILURE;
static int pending_signals[32] = {0};
enum ChildState { CHILD_NOT_STARTED, CHILD_STARTED, CHILD_STOPPED, CHILD_EXITED };
static enum ChildState child_state = CHILD_NOT_STARTED;
static void flush_data(void);
static bool
read_from_zygote(void) {
ssize_t n;
static char buf[64];
static transfer_buf t = {.buf=buf};
n = safe_read(socket_fd, t.buf + t.sz, sizeof(buf) - t.sz);
if (n < 0) {
if (errno == EIO || errno == EPIPE) { socket_fd = -1; return true; }
return false;
}
if (n) {
t.sz += n;
while (t.sz >= sizeof(long long)) {
long long val = *((long long*)t.buf);
left_shift_buffer(&t, sizeof(long long));
if (child_state == CHILD_NOT_STARTED) {
if (val == 0) { print_error("Got zero child pid from prewarm socket", 0); return false; }
child_pid = val;
child_state = CHILD_STARTED;
if (child_slave_fd > -1) { safe_close(child_slave_fd); child_slave_fd = -1; }
for (size_t i = 0; i < arraysz(pending_signals) && pending_signals[i]; i++) {
kill(child_pid, pending_signals[i]);
}
memset(pending_signals, 0, sizeof(pending_signals));
} else {
int child_exit_status = val;
if (WIFEXITED(child_exit_status)) {
exit_status = WEXITSTATUS(child_exit_status);
child_pid = 0;
child_state = CHILD_EXITED;
} else if (WIFSIGNALED(child_exit_status)) {
int signum = WTERMSIG(child_exit_status);
if (signum > 0) {
flush_data();
cleanup();
signal(signum, SIG_DFL);
raise(signum);
_exit(1);
}
} else if (WIFSTOPPED(child_exit_status)) {
child_state = CHILD_STOPPED;
int signum = WSTOPSIG(child_exit_status);
restore_termios_settings();
struct sigaction defval = {.sa_handler = SIG_DFL}, original;
sigaction(signum, &defval, &original);
raise(signum);
sigaction(signum, &original, NULL);
reapply_termios_settings();
if (child_pid > 0) {
kill(child_pid, SIGCONT);
}
child_state = CHILD_STARTED;
}
}
}
} else { socket_fd = -1; return true; }
return true;
}
static void
close_sent_fds(void) {
#define redirect(which, mode) { int fd = safe_open("/dev/null", mode | O_CLOEXEC, 0); if (fd > -1) { safe_dup2(fd, which); safe_close(fd); } }
if (stdin_pos > -1) redirect(STDIN_FILENO, O_RDONLY);
if (stdout_pos > -1) redirect(STDOUT_FILENO, O_WRONLY);
if (stderr_pos > -1) redirect(STDERR_FILENO, O_WRONLY);
#undef redirect
}
static bool
send_launch_msg(void) {
ssize_t n;
while ((n = sendmsg(socket_fd, &launch_msg_container, MSG_NOSIGNAL)) < 0 && errno == EINTR);
if (n < 0) return false;
if (n == 0) { errno = EPIPE; return false; }
// some bytes sent, null out the control msg data as it is already sent
launch_msg_container.msg_controllen = 0;
launch_msg_container.msg_control = NULL;
if ((size_t)n > launch_msg.iov_len) {
launch_msg.iov_len = 0;
close_sent_fds();
}
else launch_msg.iov_len -= n;
launch_msg.iov_base = (char*)launch_msg.iov_base + n;
return true;
}
struct fd_to_watch {
bool want_read, want_write, want_error;
};
struct watched_fds {
struct fd_to_watch self_ttyfd, signal_read_fd, socket_fd, child_master_fd;
};
static struct watched_fds wf = {0};
static bool
read_from_tty(int *fd, transfer_buf *t) {
if (*fd < 0) return true;
if (t->sz < IO_BUZ_SZ) {
ssize_t n = safe_read(*fd, t->buf + t->sz, IO_BUZ_SZ - t->sz);
if (n < 0) {
if (errno == EPIPE || errno == EIO) { *fd = -1; return true; }
if (errno == EAGAIN) return true;
return false;
}
if (n == 0) *fd = -1; // hangup
t->sz += n;
}
return true;
}
static bool
read_from_child_tty(void) {
return read_from_tty(&child_master_fd, &from_child_tty);
}
static bool
write_to_tty(transfer_buf *src, int *dest_fd) {
if (*dest_fd < 0) return true;
if (src->sz) {
ssize_t n = safe_write(*dest_fd, src->buf, src->sz);
if (n < 0) {
if (errno == EPIPE || errno == EIO) { *dest_fd = -1; return true; }
return false;
}
if (n > 0) {
left_shift_buffer(src, n);
} else *dest_fd = -1;
}
return true;
}
static bool
from_child_to_self(void) {
return write_to_tty(&from_child_tty, &self_ttyfd);
}
static bool
from_self_to_child(void) {
return write_to_tty(&to_child_tty, &child_master_fd);
}
static bool
read_from_self_tty(void) {
return read_from_tty(&self_ttyfd, &to_child_tty);
}
static bool window_size_dirty = false;
static bool
read_signals(void) {
static char buf[sizeof(siginfo_t) * 8];
static transfer_buf b = {.buf=buf};
ssize_t len = safe_read(signal_read_fd, buf + b.sz, sizeof(buf) - b.sz);
if (len < 0) return false;
if (len == 0) return true;
b.sz += len;
while (b.sz >= sizeof(siginfo_t)) {
siginfo_t *sig = (siginfo_t*)buf;
switch(sig->si_signo) {
case SIGWINCH:
window_size_dirty = true; break;
case SIGINT: case SIGTERM: case SIGHUP: case SIGQUIT: case SIGTSTP:
if (child_pid > 0) kill(child_pid, sig->si_signo);
else {
for (size_t i = 0; i < arraysz(pending_signals); i++) {
if (!pending_signals[i]) {
pending_signals[i] = sig->si_signo;
break;
}
}
}
break;
}
left_shift_buffer(&b, sizeof(siginfo_t));
}
return true;
}
static bool
keep_going(void) {
switch(child_state) {
case CHILD_NOT_STARTED:
return self_ttyfd > -1 && signal_read_fd > -1 && socket_fd > -1 && child_master_fd > -1;
case CHILD_STARTED:
case CHILD_STOPPED:
return self_ttyfd > -1 && signal_read_fd > -1 && socket_fd > -1;
case CHILD_EXITED:
return self_ttyfd > -1 && signal_read_fd > -1 && child_master_fd > -1;
}
return false;
}
static void
flush_data(void) {
if (self_ttyfd > -1 && from_child_tty.sz > 0) {
set_blocking(self_ttyfd, false);
from_child_to_self();
}
if (child_master_fd > -1 && from_child_tty.sz < IO_BUZ_SZ) {
set_blocking(child_master_fd, false);
read_from_child_tty();
}
if (self_ttyfd > -1 && from_child_tty.sz > 0) {
from_child_to_self();
}
}
static char sosbuf[2 * sizeof(self_winsize)] = {0};
static transfer_buf send_on_socket = {.buf=sosbuf};
static void
add_window_size_to_buffer(void) {
char *p;
if (send_on_socket.sz % sizeof(self_winsize)) {
// partial send
if (send_on_socket.sz > sizeof(self_winsize)) send_on_socket.sz -= sizeof(self_winsize); // replace second size
p = send_on_socket.buf + send_on_socket.sz;
send_on_socket.sz += sizeof(self_winsize);
} else {
// replace all sizes
p = send_on_socket.buf;
send_on_socket.sz = sizeof(self_winsize);
}
memcpy(p, &self_winsize, sizeof(self_winsize));
}
static bool
send_over_socket(void) {
if (!send_on_socket.sz || socket_fd < 0) return true;
ssize_t n = safe_send(socket_fd, send_on_socket.buf, send_on_socket.sz, MSG_NOSIGNAL);
if (n < 0) return false;
if (n) {
if (n >= send_on_socket.sz) send_on_socket.sz = 0;
else {
left_shift_buffer(&send_on_socket, n);
}
}
return true;
}
static void
set_append_mode(int fd, bool yes) {
int val = fcntl(fd, F_GETFL);
if (yes) val |= O_APPEND; else val &= ~O_APPEND;
fcntl(fd, F_SETFL, val);
}
#ifndef HAS_SPLICE
#define splice(...) -1
#endif
static bool
transfer_in_kernel(int from, int to, bool *read_failed) {
if (has_splice) {
ssize_t n = 0;
while ((n = splice(from, NULL, to, NULL, PIPE_BUF, 0)) == -1 && errno == EINTR);
*read_failed = n == 0;
return n > 0;
}
return false;
}
static void
loop(void) {
#define fail(s) { print_error(s, errno); return; }
int ret, nfds = 0;
#define init(which) wf.which.want_read = true; nfds = MAX(which, nfds);
init(self_ttyfd); init(signal_read_fd); init(socket_fd); init(child_master_fd);
#undef init
fd_set readable, writable, errorable;
nfds++;
struct { bool data_available_from_child, data_available_from_self; } sd;
if (has_splice) { set_append_mode(child_master_fd, false); set_append_mode(self_ttyfd, false); }
while (keep_going()) {
if (window_size_dirty) {
if (!get_window_size()) fail("getting window size for self tty failed");
// macOS barfs with ENOTTY if we try to use TIOCSWINSZ from this process, so send it to the zygote
/* if (!safe_winsz(child_master_fd, TIOCSWINSZ, &self_winsize)) fail("setting window size on child pty failed"); */
add_window_size_to_buffer();
window_size_dirty = false;
}
const bool can_splice = has_splice && child_master_fd > -1 && self_ttyfd > -1;
static bool splice_ok = true;
if (can_splice && splice_ok) {
wf.self_ttyfd.want_read = !sd.data_available_from_self;
wf.self_ttyfd.want_write = sd.data_available_from_child;
wf.child_master_fd.want_read = !sd.data_available_from_child;
wf.child_master_fd.want_write = sd.data_available_from_self;
if (!splice_ok) { set_append_mode(self_ttyfd, true); set_append_mode(child_master_fd, true); }
} else {
wf.self_ttyfd.want_read = to_child_tty.sz < IO_BUZ_SZ; wf.self_ttyfd.want_write = from_child_tty.sz > 0;
wf.child_master_fd.want_read = from_child_tty.sz < IO_BUZ_SZ; wf.child_master_fd.want_write = to_child_tty.sz > 0;
}
wf.socket_fd.want_write = launch_msg.iov_len > 0 || send_on_socket.sz > 0;
FD_ZERO(&readable); FD_ZERO(&writable); FD_ZERO(&errorable);
#define set(which) if (which > -1) { if (wf.which.want_read) { FD_SET(which, &readable); } if (wf.which.want_write) { FD_SET(which, &writable); } if (wf.which.want_error) { FD_SET(which, &errorable); } }
set(self_ttyfd); set(child_master_fd); set(socket_fd); set(signal_read_fd);
#undef set
while ((ret = select(nfds, &readable, &writable, &errorable, NULL)) == -1) { if (errno != EINTR) fail("select() failed"); }
if (!ret) continue;
if (can_splice && splice_ok) {
bool read_failed = false;
if (FD_ISSET(self_ttyfd, &readable)) sd.data_available_from_self = true;
if (sd.data_available_from_self && FD_ISSET(child_master_fd, &writable)) {
splice_ok = transfer_in_kernel(self_ttyfd, child_master_fd, &read_failed);
sd.data_available_from_self = false;
if (read_failed) fail("reading in kernel from self tty failed");
}
if (FD_ISSET(child_master_fd, &readable)) sd.data_available_from_child = true;
if (sd.data_available_from_child && FD_ISSET(self_ttyfd, &writable) && splice_ok) {
splice_ok = transfer_in_kernel(child_master_fd, self_ttyfd, &read_failed);
sd.data_available_from_child = false;
if (read_failed) fail("reading in kernel from child tty failed");
}
} else {
if (child_master_fd > -1) {
if (FD_ISSET(child_master_fd, &writable)) if (!from_self_to_child()) fail("writing to child tty failed");
if (FD_ISSET(child_master_fd, &readable)) {
if (!read_from_child_tty()) fail("reading from child tty failed");
}
}
if (self_ttyfd > -1) {
if (FD_ISSET(self_ttyfd, &readable)) if (!read_from_self_tty()) fail("reading from self tty failed");
if (FD_ISSET(self_ttyfd, &writable)) if (!from_child_to_self()) fail("writing to self tty failed");
}
}
if (signal_read_fd > -1 && FD_ISSET(signal_read_fd, &readable)) if (!read_signals()) fail("reading from signal fd failed");
if (socket_fd > -1) {
if (FD_ISSET(socket_fd, &writable)) {
if (launch_msg.iov_len > 0) { if (!send_launch_msg()) fail("sending launch message failed"); }
else if (send_on_socket.sz > 0) { if (!send_over_socket()) fail("sending on socket failed"); }
}
if (FD_ISSET(socket_fd, &readable)) {
if (!read_from_zygote()) fail("reading information about child failed");
if (socket_fd < 0) { // hangup
child_pid = 0;
child_state = CHILD_EXITED;
}
}
}
}
#undef fail
}
static char*
check_socket_addr(char *addr) {
char *p = strchr(addr, ':');
if (!p) return NULL;
*p = 0;
long val = -1;
bool ok = parse_long(addr, &val);
*p = ':';
if (!ok || val != geteuid()) return NULL;
addr = p + 1;
p = strchr(addr, ':');
if (!p) return NULL;
*p = 0;
ok = parse_long(addr, &val);
*p = ':';
if (!ok || val != getegid()) return NULL;
return p + 1;
}
void
use_prewarmed_process(int argc, char *argv[], char *envp[]) {
char *env_addr = getenv("KITTY_PREWARM_SOCKET");
if (!env_addr || !*env_addr || !is_prewarmable(argc, argv)) return;
env_addr = check_socket_addr(env_addr);
if (!env_addr) return;
self_ttyfd = safe_open(ctermid(NULL), O_RDWR | O_NONBLOCK, 0);
if (self_ttyfd < 0) return;
setup_stdio_handles();
#define fail(s) { print_error(s, errno); cleanup(); return; }
if (!setup_signal_handler()) fail("Failed to setup signal handling");
if (!get_window_size()) fail("Failed to get window size of controlling terminal");
if (!get_termios_state(&self_termios)) fail("Failed to get termios state of controlling terminal");
if (!open_pty()) fail("Failed to open slave pty");
memcpy(&restore_termios, &self_termios, sizeof(restore_termios));
termios_needs_restore = true;
cfmakeraw(&self_termios);
if (!safe_tcsetattr(self_ttyfd, TCSANOW, &self_termios)) fail("Failed to put tty into raw mode");
if (!create_launch_msg(argc, argv)) fail("Failed to create launch message");
socket_fd = connect_to_socket_synchronously(env_addr);
if (socket_fd < 0) fail("Failed to connect to prewarm socket");
#ifdef KITTY_USE_SO_NOSIGPIPE
{
int val = 1;
if (setsockopt(socket_fd, SOL_SOCKET, SO_NOSIGPIPE, (void*)&val, sizeof(val)) != 0) fail("Failed to set SO_NOSIGPIPE");
}
#endif
from_child_tty.buf = malloc(IO_BUZ_SZ * 2);
if (!from_child_tty.buf) fail("Out of memory allocating IO buffer");
to_child_tty.buf = from_child_tty.buf + IO_BUZ_SZ;
#undef fail
while (*envp) {
char *p = *envp;
const char *q = "KITTY_PREWARM_SOCKET_REAL_TTY=";
while (*p && *q && *p == *q) {
if (*p == '=') {
p++;
snprintf(p, strlen(p) + 1, "%s", child_tty_name);
break;
}
p++; q++;
}
envp++;
}
loop();
flush_data();
cleanup();
exit(exit_status);
}

View File

@ -1,15 +1,12 @@
#!/usr/bin/env python
# License: GPLv3 Copyright: 2022, Kovid Goyal <kovid at kovidgoyal.net>
import errno
import fcntl
import io
import json
import os
import select
import signal
import socket
import struct
import sys
import termios
import time
@ -27,14 +24,14 @@ from typing import (
from kitty.constants import kitty_exe, running_in_kitty
from kitty.entry_points import main as main_entry_point
from kitty.fast_data_types import (
CLD_EXITED, CLD_KILLED, CLD_STOPPED, get_options, getpeereid,
install_signal_handlers, read_signals, remove_signal_handlers, safe_pipe,
set_options, set_use_os_log
CLD_EXITED, CLD_KILLED, CLD_STOPPED, get_options, install_signal_handlers,
read_signals, remove_signal_handlers, safe_pipe, set_options,
set_use_os_log
)
from kitty.options.types import Options
from kitty.shm import SharedMemory
from kitty.types import SignalInfo
from kitty.utils import log_error, random_unix_socket, safer_fork
from kitty.utils import log_error, safer_fork
if TYPE_CHECKING:
from _typeshed import ReadableBuffer, WriteableBuffer
@ -90,7 +87,6 @@ class PrewarmProcess:
to_prewarm_stdin: int,
from_prewarm_stdout: int,
from_prewarm_death_notify: int,
unix_socket_name: str,
) -> None:
self.children: Dict[int, Child] = {}
self.worker_pid = prewarm_process_pid
@ -99,10 +95,6 @@ class PrewarmProcess:
self.read_from_process_fd = from_prewarm_stdout
self.poll = select.poll()
self.poll.register(self.read_from_process_fd, select.POLLIN)
self.unix_socket_name = unix_socket_name
def socket_env_var(self) -> str:
return f'{os.geteuid()}:{os.getegid()}:{self.unix_socket_name}'
def take_from_worker_fd(self, create_file: bool = False) -> int:
if create_file:
@ -375,20 +367,6 @@ def fork(shm_address: str, free_non_child_resources: Callable[[], None]) -> Tupl
return 0, -1 # type: ignore
def verify_socket_creds(conn: socket.socket) -> bool:
# needed as abstract unix sockets used on Linux have no permissions and
# older BSDs ignore socket file permissions
uid, gid = getpeereid(conn.fileno())
return uid == os.geteuid() and gid == os.getegid()
class SocketChildData:
def __init__(self) -> None:
self.cwd = self.tty_name = ''
self.argv: List[str] = []
self.env: Dict[str, str] = {}
Funtion = TypeVar('Funtion', bound=Callable[..., Any])
@ -421,218 +399,7 @@ interactive_and_job_control_signals = (
)
def fork_socket_child(child_data: SocketChildData, tty_fd: int, stdio_fds: Dict[str, int], free_non_child_resources: Callable[[], None]) -> int:
# see https://www.gnu.org/software/libc/manual/html_node/Launching-Jobs.html
child_pid = safer_fork()
if child_pid:
return child_pid
# child process
eintr_retry(os.setpgid)(0, 0)
eintr_retry(os.tcsetpgrp)(tty_fd, eintr_retry(os.getpgid)(0))
for x in interactive_and_job_control_signals:
signal.signal(x, signal.SIG_DFL)
restore_python_signal_handlers()
# the std streams fds are closed in free_non_child_resources()
for which in ('stdin', 'stdout', 'stderr'):
fd = stdio_fds[which] if stdio_fds[which] > -1 else tty_fd
safe_dup2(fd, getattr(sys, which).fileno())
free_non_child_resources()
child_main({'cwd': child_data.cwd, 'env': child_data.env, 'argv': child_data.argv}, prewarm_type='socket')
def fork_socket_child_supervisor(conn: socket.socket, free_non_child_resources: Callable[[], None]) -> None:
import array
global is_zygote
if safer_fork():
conn.close()
return
is_zygote = False
os.setsid()
restore_python_signal_handlers()
free_non_child_resources()
signal_read_fd = install_signal_handlers(signal.SIGCHLD, signal.SIGUSR1)[0]
# See https://www.gnu.org/software/libc/manual/html_node/Initializing-the-Shell.html
for x in interactive_and_job_control_signals:
signal.signal(x, signal.SIG_IGN)
poll = select.poll()
poll.register(signal_read_fd, select.POLLIN)
from_socket_buf = b''
to_socket_buf = b''
keep_going = True
child_pid = -1
socket_fd = conn.fileno()
launch_msg_read = False
os.set_blocking(socket_fd, False)
received_fds: List[int] = []
stdio_positions = dict.fromkeys(('stdin', 'stdout', 'stderr'), -1)
stdio_fds = dict.fromkeys(('stdin', 'stdout', 'stderr'), -1)
winsize = 8
exit_after_write = False
child_data = SocketChildData()
def handle_signal(siginfo: SignalInfo) -> None:
nonlocal to_socket_buf, exit_after_write, child_pid
if siginfo.si_signo != signal.SIGCHLD or siginfo.si_code not in (CLD_KILLED, CLD_EXITED, CLD_STOPPED):
return
while True:
try:
pid, status = os.waitpid(-1, os.WNOHANG | os.WUNTRACED)
except ChildProcessError:
pid = 0
if not pid:
break
if pid != child_pid:
continue
to_socket_buf += struct.pack('q', status)
if not os.WIFSTOPPED(status):
exit_after_write = True
child_pid = -1
def write_to_socket() -> None:
nonlocal keep_going, to_socket_buf, keep_going
buf = memoryview(to_socket_buf)
while buf:
try:
n = os.write(socket_fd, buf)
except OSError:
n = 0
if n == 0:
keep_going = False
return
buf = buf[n:]
to_socket_buf = bytes(buf)
if exit_after_write and not to_socket_buf:
keep_going = False
def read_winsize() -> None:
nonlocal from_socket_buf
msg = conn.recv(io.DEFAULT_BUFFER_SIZE)
if not msg:
return
from_socket_buf += msg
data = memoryview(from_socket_buf)
record = memoryview(b'')
while len(data) >= winsize:
record, data = data[:winsize], data[winsize:]
if record:
try:
with open(safe_open(os.ctermid(), os.O_RDWR | os.O_CLOEXEC), 'w') as f:
safe_ioctl(f.fileno(), termios.TIOCSWINSZ, record)
except OSError:
traceback.print_exc()
from_socket_buf = bytes(data)
def read_launch_msg() -> bool:
nonlocal keep_going, from_socket_buf, launch_msg_read, winsize
try:
msg, ancdata, flags, addr = conn.recvmsg(io.DEFAULT_BUFFER_SIZE, 1024)
except OSError as e:
if e.errno == errno.ENOMEM:
# macOS does this when no ancilliary data is present
msg, ancdata, flags, addr = conn.recvmsg(io.DEFAULT_BUFFER_SIZE)
else:
raise
for cmsg_level, cmsg_type, cmsg_data in ancdata:
if cmsg_level == socket.SOL_SOCKET and cmsg_type == socket.SCM_RIGHTS:
fds = array.array("i") # Array of ints
# Append data, ignoring any truncated integers at the end.
fds.frombytes(cmsg_data[:len(cmsg_data) - (len(cmsg_data) % fds.itemsize)])
received_fds.extend(fds)
if not msg:
return False
from_socket_buf += msg
while (idx := from_socket_buf.find(b'\0')) > -1:
line = from_socket_buf[:idx].decode('utf-8')
from_socket_buf = from_socket_buf[idx+1:]
cmd, _, payload = line.partition(':')
if cmd == 'finish':
for x in received_fds:
os.set_inheritable(x, True)
os.set_blocking(x, True)
for k, pos in stdio_positions.items():
if pos > -1:
stdio_fds[k] = received_fds[pos]
del received_fds[:]
return True
elif cmd == 'cwd':
child_data.cwd = payload
elif cmd == 'env':
k, _, v = payload.partition('=')
child_data.env[k] = v
elif cmd == 'argv':
child_data.argv.append(payload)
elif cmd in stdio_positions:
stdio_positions[cmd] = int(payload)
elif cmd == 'tty_name':
child_data.tty_name = payload
elif cmd == 'winsize':
winsize = int(payload)
return False
def free_non_child_resources2() -> None:
for fd in received_fds:
safe_close(fd)
for k, v in tuple(stdio_fds.items()):
if v > -1:
safe_close(v)
stdio_fds[k] = -1
conn.close()
def launch_child() -> None:
nonlocal to_socket_buf, child_pid
sys.__stdout__.flush()
sys.__stderr__.flush()
tty_fd = establish_controlling_tty(child_data.tty_name, closefd=False)
child_pid = fork_socket_child(child_data, tty_fd, stdio_fds, free_non_child_resources2)
if child_pid:
# this is also done in the child process, but we dont
# know when, so do it here as well
eintr_retry(os.setpgid)(child_pid, child_pid)
eintr_retry(os.tcsetpgrp)(tty_fd, child_pid)
for fd in stdio_fds.values():
if fd > -1:
safe_close(fd)
safe_close(tty_fd)
else:
raise SystemExit('fork_socket_child() returned in the child process')
to_socket_buf += struct.pack('q', child_pid)
def read_from_socket() -> None:
nonlocal launch_msg_read
if launch_msg_read:
read_winsize()
else:
if read_launch_msg():
launch_msg_read = True
launch_child()
try:
while keep_going:
poll.register(socket_fd, select.POLLIN | (select.POLLOUT if to_socket_buf else 0))
for fd, event in poll.poll():
if event & error_events:
keep_going = False
break
if fd == socket_fd:
if event & select.POLLOUT:
write_to_socket()
if event & select.POLLIN:
read_from_socket()
elif fd == signal_read_fd and event & select.POLLIN:
read_signals(signal_read_fd, handle_signal)
finally:
if child_pid: # supervisor process
with suppress(OSError):
conn.shutdown(socket.SHUT_RDWR)
with suppress(OSError):
conn.close()
raise SystemExit(0)
def main(stdin_fd: int, stdout_fd: int, notify_child_death_fd: int, unix_socket: socket.socket) -> None:
def main(stdin_fd: int, stdout_fd: int, notify_child_death_fd: int) -> None:
global parent_tty_name
with suppress(OSError):
parent_tty_name = os.ttyname(sys.stdout.fileno())
@ -640,12 +407,9 @@ def main(stdin_fd: int, stdout_fd: int, notify_child_death_fd: int, unix_socket:
os.set_blocking(stdin_fd, False)
os.set_blocking(stdout_fd, False)
signal_read_fd = install_signal_handlers(signal.SIGCHLD, signal.SIGUSR1)[0]
os.set_blocking(unix_socket.fileno(), False)
unix_socket.listen(5)
poll = select.poll()
poll.register(stdin_fd, select.POLLIN)
poll.register(signal_read_fd, select.POLLIN)
poll.register(unix_socket.fileno(), select.POLLIN)
input_buf = output_buf = child_death_buf = b''
child_ready_fds: Dict[int, int] = {}
child_pid_map: Dict[int, int] = {}
@ -666,7 +430,6 @@ def main(stdin_fd: int, stdout_fd: int, notify_child_death_fd: int, unix_socket:
for fd in get_all_non_child_fds():
if fd > -1:
safe_close(fd)
unix_socket.close()
def check_event(event: int, err_msg: str) -> None:
if event & select.POLLHUP:
@ -766,15 +529,6 @@ def main(stdin_fd: int, stdout_fd: int, notify_child_death_fd: int, unix_socket:
read_signals(signal_read_fd, handle_signal)
def handle_socket_client(event: int) -> None:
check_event(event, 'UNIX socket fd listener failed')
conn, addr = unix_socket.accept()
if not verify_socket_creds(conn):
print_error('Connection attempted with invalid credentials ignoring')
conn.close()
return
fork_socket_child_supervisor(conn, free_non_child_resources)
keep_type_checker_happy = True
try:
while is_zygote and keep_type_checker_happy:
@ -791,8 +545,6 @@ def main(stdin_fd: int, stdout_fd: int, notify_child_death_fd: int, unix_socket:
handle_signals(event)
elif q == notify_child_death_fd:
handle_notify_child_death(event)
elif q == unix_socket.fileno():
handle_socket_client(event)
except (KeyboardInterrupt, EOFError, BrokenPipeError):
if is_zygote:
raise SystemExit(1)
@ -809,33 +561,18 @@ def main(stdin_fd: int, stdout_fd: int, notify_child_death_fd: int, unix_socket:
safe_close(fmd)
def get_socket_name(unix_socket: socket.socket) -> str:
sname = unix_socket.getsockname()
if isinstance(sname, bytes):
sname = sname.decode('utf-8')
assert isinstance(sname, str)
if sname.startswith('\0'):
sname = '@' + sname[1:]
return sname
def exec_main(stdin_read: int, stdout_write: int, death_notify_write: int, unix_socket: Optional[socket.socket] = None) -> None:
def exec_main(stdin_read: int, stdout_write: int, death_notify_write: int) -> None:
os.setsid()
os.set_inheritable(stdin_read, False)
os.set_inheritable(stdout_write, False)
os.set_inheritable(death_notify_write, False)
running_in_kitty(False)
if unix_socket is None:
unix_socket = random_unix_socket()
os.write(stdout_write, f'{get_socket_name(unix_socket)}\n'.encode('utf-8'))
if not sys.stdout.line_buffering: # happens if the parent kitty instance has stdout not pointing to a terminal
sys.stdout.reconfigure(line_buffering=True) # type: ignore
try:
main(stdin_read, stdout_write, death_notify_write, unix_socket)
main(stdin_read, stdout_write, death_notify_write)
finally:
set_options(None)
if is_zygote:
unix_socket.close()
def fork_prewarm_process(opts: Options, use_exec: bool = False) -> Optional[PrewarmProcess]:
@ -850,21 +587,15 @@ def fork_prewarm_process(opts: Options, use_exec: bool = False) -> Optional[Prew
child_pid = tp.pid
tp.returncode = 0 # prevent a warning when the popen object is deleted with the process still running
os.set_blocking(stdout_read, True)
with open(stdout_read, 'rb', closefd=False) as f:
socket_name = f.readline().decode('utf-8').rstrip()
os.set_blocking(stdout_read, False)
else:
unix_socket = random_unix_socket()
socket_name = get_socket_name(unix_socket)
child_pid = safer_fork()
if child_pid:
# master
if not use_exec:
unix_socket.close()
safe_close(stdin_read)
safe_close(stdout_write)
safe_close(death_notify_write)
p = PrewarmProcess(child_pid, stdin_write, stdout_read, death_notify_read, socket_name)
p = PrewarmProcess(child_pid, stdin_write, stdout_read, death_notify_read)
if use_exec:
p.reload_kitty_config()
return p
@ -874,5 +605,5 @@ def fork_prewarm_process(opts: Options, use_exec: bool = False) -> Optional[Prew
safe_close(stdout_read)
safe_close(death_notify_read)
set_options(opts)
exec_main(stdin_read, stdout_write, death_notify_write, unix_socket)
exec_main(stdin_read, stdout_write, death_notify_write)
raise SystemExit(0)

View File

@ -9,9 +9,8 @@ import signal
import subprocess
import tempfile
import time
from contextlib import suppress
from kitty.constants import kitty_exe, terminfo_dir
from kitty.constants import kitty_exe
from kitty.fast_data_types import (
CLD_EXITED, CLD_KILLED, CLD_STOPPED, get_options, has_sigqueue, install_signal_handlers,
read_signals, sigqueue
@ -24,139 +23,6 @@ class Prewarm(BaseTest):
maxDiff = None
def test_socket_prewarming(self):
from kitty.prewarm import fork_prewarm_process, wait_for_child_death
exit_code = 17
src = '''\
def socket_child_main(exit_code=0, initial_print=''):
import os, sys, json, signal
from kitty.fast_data_types import get_options
from kitty.utils import read_screen_size
def report_screen_size_change(*a):
print("Screen size changed:", read_screen_size(fd=sys.stderr.fileno()).cols, file=sys.stderr, flush=True)
def report_tstp(*a):
print("SIGTSTP received", file=sys.stderr, flush=True)
raise SystemExit(19)
signal.signal(signal.SIGWINCH, report_screen_size_change)
signal.signal(signal.SIGTSTP, report_tstp)
if initial_print:
print(initial_print, flush=True, file=sys.stderr)
output = {
'test_env': os.environ.get('TEST_ENV_PASS', ''),
'cwd': os.path.realpath(os.getcwd()),
'font_family': get_options().font_family,
'cols': read_screen_size(fd=sys.stderr.fileno()).cols,
'stdin_data': sys.stdin.read(),
'done': 'hello',
}
print(json.dumps(output, indent=2), file=sys.stderr, flush=True)
print('testing stdout', end='')
raise SystemExit(exit_code)
''' + '\n\n'
cwd = os.path.realpath(tempfile.gettempdir())
opts = self.set_options()
opts.config_overrides = 'font_family prewarm',
p = fork_prewarm_process(opts, use_exec=True)
if p is None:
return
env = os.environ.copy()
env.update({
'TEST_ENV_PASS': 'xyz',
'KITTY_PREWARM_SOCKET': p.socket_env_var(),
'KITTY_PREWARM_SOCKET_REAL_TTY': ' ' * 32,
'TERM': 'xterm-kitty',
'TERMINFO': terminfo_dir
})
cols = 117
def wait_for_death(exit_code, timeout=5):
status = wait_for_child_death(pty.child_pid, timeout=timeout)
if status is None:
os.kill(pty.child_pid, signal.SIGKILL)
if status is None:
pty.process_input_from_child(0)
self.assertIsNotNone(status, f'prewarm wrapper process did not exit. Screen contents: {pty.screen_contents()}')
if isinstance(exit_code, signal.Signals):
self.assertTrue(os.WIFSIGNALED(status), 'prewarm wrapper did not die with a signal')
self.assertEqual(os.WTERMSIG(status), exit_code.value)
else:
with suppress(AttributeError):
self.assertEqual(os.waitstatus_to_exitcode(status), exit_code, pty.screen_contents())
if not self.is_ci: # signal delivery tests are pretty flakey on CI so give up on them
with self.subTest(msg='test SIGINT via signal to wrapper'):
pty = self.create_pty(
argv=[kitty_exe(), '+runpy', src + 'socket_child_main(initial_print="child ready:")'], cols=cols, env=env, cwd=cwd)
pty.wait_till(lambda: 'child ready:' in pty.screen_contents())
os.kill(pty.child_pid, signal.SIGINT)
pty.wait_till(lambda: 'KeyboardInterrupt' in pty.screen_contents())
wait_for_death(signal.SIGINT)
with self.subTest(msg='test SIGINT via Ctrl-c'):
pty = self.create_pty(
argv=[kitty_exe(), '+runpy', src + 'socket_child_main(initial_print="child ready:")'], cols=cols, env=env, cwd=cwd)
pty.wait_till(lambda: 'child ready:' in pty.screen_contents())
pty.write_to_child('\x03', flush=True)
pty.wait_till(lambda: 'KeyboardInterrupt' in pty.screen_contents())
wait_for_death(signal.SIGINT)
with self.subTest(msg='test SIGTSTP via Ctrl-z'):
pty = self.create_pty(
argv=[kitty_exe(), '+runpy', src + 'socket_child_main(initial_print="child ready:")'], cols=cols, env=env, cwd=cwd)
pty.wait_till(lambda: 'child ready:' in pty.screen_contents())
pty.write_to_child('\x1a', flush=True)
pty.wait_till(lambda: 'SIGTSTP received' in pty.screen_contents())
wait_for_death(19)
with self.subTest(msg='test SIGWINCH handling'):
pty = self.create_pty(
argv=[kitty_exe(), '+runpy', src + 'socket_child_main(initial_print="child ready:")'], cols=cols, env=env, cwd=cwd)
pty.wait_till(lambda: 'child ready:' in pty.screen_contents())
pty.set_window_size(columns=cols + 3)
pty.wait_till(lambda: f'Screen size changed: {cols + 3}' in pty.screen_contents())
os.close(pty.master_fd)
with self.subTest(msg='test env rewrite'):
pty = self.create_pty(
argv=[kitty_exe(), '+runpy', src + 'socket_child_main(initial_print="child ready:")'], cols=cols, env=env, cwd=cwd)
pty.wait_till(lambda: 'child ready:' in pty.screen_contents())
from kitty.child import environ_of_process
self.assertIn('/', environ_of_process(pty.child_pid).get('KITTY_PREWARM_SOCKET_REAL_TTY', ''))
os.close(pty.master_fd)
with self.subTest(msg='test passing of data via cwd, env vars and stdin/stdout redirection'):
stdin_r, stdin_w = os.pipe()
os.set_inheritable(stdin_w, False)
stdout_r, stdout_w = os.pipe()
os.set_inheritable(stdout_r, False)
pty = self.create_pty(
argv=[kitty_exe(), '+runpy', src + f'socket_child_main({exit_code})'], cols=cols, env=env, cwd=cwd,
stdin_fd=stdin_r, stdout_fd=stdout_w)
stdin_data = 'testing--stdin-read'
with open(stdin_w, 'w') as f:
f.write(stdin_data)
def has_json():
s = pty.screen_contents().strip()
return 'hello' in s and s.endswith('}')
pty.wait_till(has_json)
wait_for_death(exit_code)
output = json.loads(pty.screen_contents().strip())
self.assertEqual(output['test_env'], env['TEST_ENV_PASS'])
self.assertEqual(output['cwd'], cwd)
self.assertEqual(output['font_family'], 'prewarm')
self.assertEqual(output['cols'], cols)
self.assertEqual(output['stdin_data'], stdin_data)
with open(stdout_r) as f:
stdout_data = f.read()
self.assertEqual(stdout_data, 'testing stdout')
def test_prewarming(self):
from kitty.prewarm import fork_prewarm_process

View File

@ -920,7 +920,7 @@ def build_launcher(args: Options, launcher_dir: str = '.', bundle_type: str = 's
os.makedirs(launcher_dir, exist_ok=True)
os.makedirs(build_dir, exist_ok=True)
objects = []
for src in ('kitty/launcher/main.c', 'kitty/launcher/prewarm.c'):
for src in ('kitty/launcher/main.c',):
obj = os.path.join(build_dir, src.replace('/', '-').replace('.c', '.o'))
objects.append(obj)
cmd = env.cc + cppflags + cflags + ['-c', src, '-o', obj]

View File

@ -3,7 +3,6 @@
import importlib
import os
import sys
import warnings
from contextlib import contextmanager
from tempfile import TemporaryDirectory
@ -25,9 +24,6 @@ def env_vars(**kw: str) -> Iterator[None]:
def main() -> None:
if 'prewarmed' in getattr(sys, 'kitty_run_data'):
os.environ.pop('KITTY_PREWARM_SOCKET')
os.execlp(sys.executable, sys.executable, '+launch', __file__, *sys.argv[1:])
warnings.simplefilter('error')
current_home = os.path.expanduser('~') + os.sep
paths = os.environ.get('PATH', '/usr/local/sbin:/usr/local/bin:/usr/bin').split(os.pathsep)