Skip to content

Commit

Permalink
Win32: Thread-safe windows console output
Browse files Browse the repository at this point in the history
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]>
  • Loading branch information
kblees authored and gitster committed Jun 10, 2014
1 parent 1c950a5 commit eac14f8
Show file tree
Hide file tree
Showing 3 changed files with 273 additions and 149 deletions.
9 changes: 6 additions & 3 deletions compat/mingw.c
Original file line number Diff line number Diff line change
Expand Up @@ -865,9 +865,9 @@ static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **env,
memset(&si, 0, sizeof(si));
si.cb = sizeof(si);
si.dwFlags = STARTF_USESTDHANDLES;
si.hStdInput = (HANDLE) _get_osfhandle(fhin);
si.hStdOutput = (HANDLE) _get_osfhandle(fhout);
si.hStdError = (HANDLE) _get_osfhandle(fherr);
si.hStdInput = winansi_get_osfhandle(fhin);
si.hStdOutput = winansi_get_osfhandle(fhout);
si.hStdError = winansi_get_osfhandle(fherr);

/* concatenate argv, quoting args as we go */
strbuf_init(&args, 0);
Expand Down Expand Up @@ -1946,4 +1946,7 @@ void mingw_startup()
_setmode(_fileno(stdin), _O_BINARY);
_setmode(_fileno(stdout), _O_BINARY);
_setmode(_fileno(stderr), _O_BINARY);

/* initialize Unicode console */
winansi_init();
}
12 changes: 4 additions & 8 deletions compat/mingw.h
Original file line number Diff line number Diff line change
Expand Up @@ -317,14 +317,10 @@ int mingw_raise(int sig);
* ANSI emulation wrappers
*/

int winansi_fputs(const char *str, FILE *stream);
int winansi_printf(const char *format, ...) __attribute__((format (printf, 1, 2)));
int winansi_fprintf(FILE *stream, const char *format, ...) __attribute__((format (printf, 2, 3)));
int winansi_vfprintf(FILE *stream, const char *format, va_list list);
#define fputs winansi_fputs
#define printf(...) winansi_printf(__VA_ARGS__)
#define fprintf(...) winansi_fprintf(__VA_ARGS__)
#define vfprintf winansi_vfprintf
void winansi_init(void);
int winansi_isatty(int fd);
HANDLE winansi_get_osfhandle(int fd);
#define isatty winansi_isatty

/*
* git specific compatibility
Expand Down
Loading

0 comments on commit eac14f8

Please sign in to comment.