Skip to content

Commit eac14f8

Browse files
kbleesgitster
authored andcommitted
Win32: Thread-safe windows console output
Winansi.c has many static variables that are accessed and modified from the [v][f]printf / fputs functions overridden in the file. This may cause multi threaded git commands that print to the console to produce corrupted output or even crash. Additionally, winansi.c doesn't override all functions that can be used to print to the console (e.g. fwrite, write, fputc are missing), so that ANSI escapes don't work properly for some git commands (e.g. git-grep). Instead of doing ANSI emulation in just a few wrapped functions on top of the IO API, let's plug into the IO system and take advantage of the thread safety inherent to the IO system. Redirect stdout and stderr to a pipe if they point to the console. A background thread reads from the pipe, handles ANSI escape sequences and UTF-8 to UTF-16 conversion, then writes to the console. The pipe-based stdout and stderr replacements must be set to unbuffered, as MSVCRT doesn't support line buffering and fully buffered streams are inappropriate for console output. Due to the byte-oriented pipe, ANSI escape sequences and multi-byte UTF-8 sequences can no longer be expected to arrive in one piece. Replace the string-based ansi_emulate() with a simple stateful parser (this also fixes colored diff hunk headers, which were broken as of commit 2efcc97). Override isatty to return true for the pipes redirecting to the console. Exec/spawn obtain the original console handle to pass to the next process via winansi_get_osfhandle(). All other overrides are gone, the default stdio implementations work as expected with the piped stdout/stderr descriptors. Global variables are either initialized on startup (single threaded) or exclusively modified by the background thread. Threads communicate through the pipe, no further synchronization is necessary. The background thread is terminated by disonnecting the pipe after flushing the stdio and pipe buffers. This doesn't work for anonymous pipes (created via CreatePipe), as DisconnectNamedPipe only works on the read end, which discards remaining data. Thus we have to setup the pipe manually, with the write end beeing the server (opened with CreateNamedPipe) and the read end the client (opened with CreateFile). Limitations: doesn't track reopened or duped file descriptors, i.e.: - fdopen(1/2) returns fully buffered streams - dup(1/2), dup2(1/2) returns normal pipe descriptors (i.e. isatty() = false, winansi_get_osfhandle won't return the original console handle) Currently, only the git-format-patch command uses xfdopen(xdup(1)) (see "realstdout" in builtin/log.c), but works well with these limitations. Many thanks to Atsushi Nakagawa <[email protected]> for suggesting and reviewing the thread-exit-mechanism. Signed-off-by: Karsten Blees <[email protected]> Signed-off-by: Stepan Kasal <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 1c950a5 commit eac14f8

File tree

3 files changed

+273
-149
lines changed

3 files changed

+273
-149
lines changed

compat/mingw.c

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -865,9 +865,9 @@ static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **env,
865865
memset(&si, 0, sizeof(si));
866866
si.cb = sizeof(si);
867867
si.dwFlags = STARTF_USESTDHANDLES;
868-
si.hStdInput = (HANDLE) _get_osfhandle(fhin);
869-
si.hStdOutput = (HANDLE) _get_osfhandle(fhout);
870-
si.hStdError = (HANDLE) _get_osfhandle(fherr);
868+
si.hStdInput = winansi_get_osfhandle(fhin);
869+
si.hStdOutput = winansi_get_osfhandle(fhout);
870+
si.hStdError = winansi_get_osfhandle(fherr);
871871

872872
/* concatenate argv, quoting args as we go */
873873
strbuf_init(&args, 0);
@@ -1946,4 +1946,7 @@ void mingw_startup()
19461946
_setmode(_fileno(stdin), _O_BINARY);
19471947
_setmode(_fileno(stdout), _O_BINARY);
19481948
_setmode(_fileno(stderr), _O_BINARY);
1949+
1950+
/* initialize Unicode console */
1951+
winansi_init();
19491952
}

compat/mingw.h

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -317,14 +317,10 @@ int mingw_raise(int sig);
317317
* ANSI emulation wrappers
318318
*/
319319

320-
int winansi_fputs(const char *str, FILE *stream);
321-
int winansi_printf(const char *format, ...) __attribute__((format (printf, 1, 2)));
322-
int winansi_fprintf(FILE *stream, const char *format, ...) __attribute__((format (printf, 2, 3)));
323-
int winansi_vfprintf(FILE *stream, const char *format, va_list list);
324-
#define fputs winansi_fputs
325-
#define printf(...) winansi_printf(__VA_ARGS__)
326-
#define fprintf(...) winansi_fprintf(__VA_ARGS__)
327-
#define vfprintf winansi_vfprintf
320+
void winansi_init(void);
321+
int winansi_isatty(int fd);
322+
HANDLE winansi_get_osfhandle(int fd);
323+
#define isatty winansi_isatty
328324

329325
/*
330326
* git specific compatibility

0 commit comments

Comments
 (0)