Skip to content

Commit

Permalink
Run git-daemon on windows without closing socket immediately.
Browse files Browse the repository at this point in the history
git client had exit with 'read error' some time, if connected git-daemon
on windows. The cause was that git-daemon close socket immediately.
I fixed this problem.
Here is detail the explanation.

https://github.com/toshiyuki-ogawa/msysgit/wiki/A-consideration-of--'fatal:-read-error-(invalid-argument)'

Signed-off-by: Toshiyuki Ogawa <Toshiyuki.Ogawa.2.71@gmail.com>
  • Loading branch information
Toshiyuki Ogawa authored and Toshiyuki Ogawa committed May 8, 2013
1 parent 694fb72 commit 48f7556
Show file tree
Hide file tree
Showing 31 changed files with 2,213 additions and 63 deletions.
37 changes: 37 additions & 0 deletions Documentation/git-close-socket.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
git-close-socket(1)
===================

NAME
----
git-close-socket - Close socket like Linux.


SYNOPSIS
--------
[verse]
'git close-socket'

DESCRIPTION
-----------

Emulate to close socket as Linux. Some system, especialy windows, will close
soket immediately. In such system git daemon will not communicate some git
clients well.
This command resovle the problem. Command will sleep some secondes and exit.
Sleep seconds is specified below order.

Windows::
1. 'git-config --int win.sock.time.wait'
2. half of registry value 'HKLM\\CurrentControlSet\\Services\\Tcpip\\Parameters\\TcpTimedWaitDelay'
3. 240


SEE ALSO
--------
http://www.softlab.ntua.gr/facilities/documentation/unix/unix-socket-faq/unix-socket-faq-4.html#ss4.2
http://msdn.microsoft.com/en-us/library/windows/desktop/ms737582.asp
linkgit:git-config[1]

GIT
---
Part of the linkgit:git[1] suite
4 changes: 4 additions & 0 deletions Documentation/git-pack-objects.txt
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,10 @@ So does `git bundle` (see linkgit:git-bundle[1]) when it creates a bundle.
With this option, parents that are hidden by grafts are packed
nevertheless.

--shutdown-out-socket::
On exit, shutdown outgoing socket if outgoing file descript is
socket.

SEE ALSO
--------
linkgit:git-rev-list[1]
Expand Down
5 changes: 4 additions & 1 deletion Documentation/git-upload-pack.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ git-upload-pack - Send objects packed back to git-fetch-pack
SYNOPSIS
--------
[verse]
'git-upload-pack' [--strict] [--timeout=<n>] <directory>
'git-upload-pack' [--strict] [--timeout=<n>] [--shutdown-out-soket] <directory>

This comment has been minimized.

Copy link
@linquize

linquize May 9, 2013

should be [--shutdown-out-socket]


DESCRIPTION
-----------
Expand All @@ -31,6 +31,9 @@ OPTIONS
--timeout=<n>::
Interrupt transfer after <n> seconds of inactivity.

--shutdown-out-socket::
Shutdown outgoing file descripter, if file descripter is socket.

<directory>::
The repository to sync from.

Expand Down
19 changes: 16 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -504,6 +504,7 @@ PROGRAM_OBJS += shell.o
PROGRAM_OBJS += show-index.o
PROGRAM_OBJS += upload-pack.o
PROGRAM_OBJS += remote-testsvn.o
PROGRAM_OBJS += close-socket.o

# Binary suffix, set to .exe for Windows builds
X =
Expand Down Expand Up @@ -532,7 +533,7 @@ TEST_PROGRAMS_NEED_X += test-sigchain
TEST_PROGRAMS_NEED_X += test-string-list
TEST_PROGRAMS_NEED_X += test-subprocess
TEST_PROGRAMS_NEED_X += test-svn-fe

TEST_PROGRAMS_NEED_X += test-env-utils
TEST_PROGRAMS = $(patsubst %,%$X,$(TEST_PROGRAMS_NEED_X))

# List built-in command $C whose implementation cmd_$C() is not in
Expand Down Expand Up @@ -630,6 +631,9 @@ LIB_H += compat/terminal.h
LIB_H += compat/win32/dirent.h
LIB_H += compat/win32/pthread.h
LIB_H += compat/win32/syslog.h
LIB_H += compat/winsock-proc.h
LIB_H += compat/winsock-utils.h
LIB_H += compat/win-fd.h
LIB_H += connected.h
LIB_H += convert.h
LIB_H += credential.h
Expand All @@ -639,6 +643,7 @@ LIB_H += delta.h
LIB_H += diff.h
LIB_H += diffcore.h
LIB_H += dir.h
LIB_H += evn-utils.h
LIB_H += exec_cmd.h
LIB_H += fetch-pack.h
LIB_H += fmt-merge-msg.h
Expand Down Expand Up @@ -688,6 +693,7 @@ LIB_H += sha1-lookup.h
LIB_H += shortlog.h
LIB_H += sideband.h
LIB_H += sigchain.h
LIB_H += socket-utils.h
LIB_H += strbuf.h
LIB_H += streaming.h
LIB_H += string-list.h
Expand Down Expand Up @@ -753,6 +759,7 @@ LIB_OBJS += dir.o
LIB_OBJS += editor.o
LIB_OBJS += entry.o
LIB_OBJS += environment.o
LIB_OBJS += env-utils.o
LIB_OBJS += exec_cmd.o
LIB_OBJS += fetch-pack.o
LIB_OBJS += fsck.o
Expand Down Expand Up @@ -818,6 +825,7 @@ LIB_OBJS += sha1_name.o
LIB_OBJS += shallow.o
LIB_OBJS += sideband.o
LIB_OBJS += sigchain.o
LIB_OBJS += socket-utils.o
LIB_OBJS += strbuf.o
LIB_OBJS += streaming.o
LIB_OBJS += string-list.o
Expand Down Expand Up @@ -1292,7 +1300,7 @@ ifeq ($(uname_S),Windows)
BASIC_CFLAGS = -nologo -I. -I../zlib -Icompat/vcbuild -Icompat/vcbuild/include -DWIN32 -D_CONSOLE -DHAVE_STRING_H -D_CRT_SECURE_NO_WARNINGS -D_CRT_NONSTDC_NO_DEPRECATE
COMPAT_OBJS = compat/msvc.o compat/winansi.o \
compat/win32/pthread.o compat/win32/syslog.o \
compat/win32/dirent.o
compat/win32/dirent.o

This comment has been minimized.

Copy link
@linquize

linquize May 9, 2013

why having a trailing space?

COMPAT_CFLAGS = -D__USE_MINGW_ACCESS -DNOGDI -DHAVE_STRING_H -DHAVE_ALLOCA_H -Icompat -Icompat/regex -Icompat/win32 -DSTRIP_EXTENSION=\".exe\"
BASIC_LDFLAGS = -IGNORE:4217 -IGNORE:4049 -NOLOGO -SUBSYSTEM:CONSOLE
EXTLIBS = user32.lib advapi32.lib shell32.lib wininet.lib ws2_32.lib
Expand Down Expand Up @@ -1441,12 +1449,17 @@ ifneq (,$(findstring MINGW,$(uname_S)))
COMPAT_CFLAGS += -DSTRIP_EXTENSION=\".exe\"
COMPAT_OBJS += compat/mingw.o compat/winansi.o \
compat/win32/pthread.o compat/win32/syslog.o \
compat/win32/dirent.o
compat/win32/dirent.o \
compat/winsock-proc.o \
compat/win-fd.o \
compat/winsock-utils.o
BASIC_LDFLAGS += -Wl,--large-address-aware
EXTLIBS += -lws2_32
GITLIBS += git.res
PTHREAD_LIBS =
RC = windres -O coff
TEST_PROGRAMS_NEED_X += test-win-fd
TEST_PROGRAMS_NEED_X += test-winsock-utils
X = .exe
SPARSE_FLAGS = -Wno-one-bit-signed-bitfield
ifneq (,$(wildcard ../THIS_IS_MSYSGIT))
Expand Down
11 changes: 8 additions & 3 deletions builtin/pack-objects.c
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
#include "refs.h"
#include "streaming.h"
#include "thread-utils.h"

#include "socket-utils.h"
static const char *pack_usage[] = {
N_("git pack-objects --stdout [options...] [< ref-list | < object-list]"),
N_("git pack-objects [options...] base-name [< ref-list | < object-list]"),
Expand Down Expand Up @@ -811,7 +811,6 @@ static void write_pack_file(void)
die("wrote %"PRIu32" objects while expecting %"PRIu32,
written, nr_result);
}

static int locate_object_entry_hash(const unsigned char *sha1)
{
int i;
Expand Down Expand Up @@ -2433,7 +2432,6 @@ static int option_parse_ulong(const struct option *opt,
#define OPT_ULONG(s, l, v, h) \
{ OPTION_CALLBACK, (s), (l), (v), "n", (h), \
PARSE_OPT_NONEG, option_parse_ulong }

int cmd_pack_objects(int argc, const char **argv, const char *prefix)
{
int use_internal_rev_list = 0;
Expand Down Expand Up @@ -2598,5 +2596,12 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
fprintf(stderr, "Total %"PRIu32" (delta %"PRIu32"),"
" reused %"PRIu32" (delta %"PRIu32")\n",
written, written_delta, reused, reused_delta);
#ifdef EMULATE_TIME_WAIT_SOCKET


if (pack_to_stdout && is_socket(1)) {
set_socket_to_time_wait(1, 1);
}
#endif
return 0;
}
33 changes: 33 additions & 0 deletions close-socket.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#include "cache.h"
#include <time.h>
#ifdef WIN32
#include "winsock-utils.h"
#endif

#ifdef EMULATE_TIME_WAIT_SOCKET
#ifdef WIN32
static int wait_for_time_out(void)
{
time_t start_time;
time_t wait_time;
start_time = time(NULL);
wait_time = get_socket_time_wait();
while (time(NULL) - start_time < wait_time) {
sleep(1);
}
return 0;
}
#endif
#else
static int wait_for_time_out(void)
{
return 0;
}
#endif
int main(int argc, char** argv)
{
int result;

result = wait_for_time_out();
return result;
}
5 changes: 5 additions & 0 deletions compat/mingw.c
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
static const int delay[] = { 0, 1, 10, 20, 40 };
unsigned int _CRT_fmode = _O_BINARY;


int err_win_to_posix(DWORD winerr)
{
int error = ENOSYS;
Expand Down Expand Up @@ -1471,6 +1472,7 @@ static void socket_cleanup(void)
ipv6_getnameinfo = getnameinfo_stub;
}


static void ensure_socket_initialization(void)
{
WSADATA wsa;
Expand Down Expand Up @@ -1515,6 +1517,9 @@ static void ensure_socket_initialization(void)
atexit(socket_cleanup);
initialized = 1;
}
void mingw_ensure_socket_initialization(void) {
ensure_socket_initialization();
}

#undef gethostname
int mingw_gethostname(char *name, int namelen)
Expand Down
4 changes: 4 additions & 0 deletions compat/mingw.h
Original file line number Diff line number Diff line change
Expand Up @@ -467,3 +467,7 @@ extern int err_win_to_posix(DWORD winerr);

extern const char *get_windows_home_directory();
#define get_home_directory() get_windows_home_directory()

#define EMULATE_TIME_WAIT_SOCKET
extern void mingw_ensure_socket_initialization();

Loading

6 comments on commit 48f7556

@dscho
Copy link

@dscho dscho commented on 48f7556 Mar 17, 2014

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a real big commit, and the subsequent commits provide some fixups.

If you don't mind, I would like to have you test a different tack first (I cannot test it because I do not encounter the read error myself): how about calling setsockopt() with SO_LINGER? It might fix the problem without many changes.

@toshiyuki-ogawa
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would be going to test again. But I don't understand "a different track". How do I test on a different track?
Off course I did try Calling "setsockopt() with SO_LINGER" on first, but it did not result what I want.
The calling "setsockopt" affect on the process. It did not work after the process dead. I need to keep opening daemon side socket after the git-upload-pack process dead.

@dscho
Copy link

@dscho dscho commented on 48f7556 Mar 18, 2014

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@toshiyuki-ogawa okay, just making sure.

The big problem I see with this commit is that it is really big. It took me quite a while to read through it, and I did not even have time to review it.

It seems that it introduces quite a few new files with quite a lot of functions that I would not deem necessary for emulating a TIME_WAIT. Besides, I really wonder why we have to linger around when we clearly do not read/write anything. Maybe we first have to make sure that the client received everything and that it is safe to close the socket now?

In any case, the final form of this pull request needs to have small, reviewable commits, and it also cannot introduce #ifdef WIN32 in the non-platform-specific code (it still does so).

So I guess the most pressing thing I would like to know: how does the git daemon know that all of its data was sent to the client, i.e. that it is safe to close the socket now?

@toshiyuki-ogawa
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Were I in your place, I think this commit is big as you do. I am going to answer some questions about this commit and modify some lines which don't keep git rules.
The most important thing is that git client exit without 'read error' in any communication environment. I don't persist this commit but strongly hope to fix 'read error' bug.

I think that emulating TIME_WAIT is more natural than another ways. In git, some small processes archive a user request. In my option, this is git software design. I want to keep the style. When I was fixing this problem, I had have an another fixing way which was to call 'setsockopt' and make git-daemon wait until child process dead. But I felt that it didn't keep the style. I expected that it was more complex modification of daemon.c than this commit. If child process inherit socket attributes, this bug fix will be simpler than this commit.

In my opinion git-daemon would have no way to know that client received all data. Whereas git-upload-pack flush all data on exit. At that time all data is in server side socket buffer. I think the buffer is in kernel on unix like os, on the other hand it is in process on windows. In windows, the buffer is disposed on process exit. In unix like os, the buffer remain in kernel for few minutes. I think unix like os is optimistic way that client will recieve all data in few minutes after server side process exit, Windows os way is designed for multi-thread environment in single process.

I have two options about this pull request.
First one, to keep git rules, write some functions platform-specific code on compat/mingw.c and remove '#ifdef WIN32' from non-platform-specific code.

Second one, How about to create a branch to check 'read error', try calling setsockopt linger and resolve 'read error' step by step? This branch has some tags which are represent check points. If you approve of this proposal, I am going to create the branch. It will take some weeks to pull request, because I have one or two hours every week to work on the branch.

@dscho
Copy link

@dscho dscho commented on 48f7556 Mar 21, 2014

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@toshiyuki-ogawa thank you for your detailed answer!

About the time constraints: I hear you. I myself cannot spend more than 15 minutes per day on average (i.e. about the same time you can spare) for working on Git.

Maybe a better way to tackle this would be to write a simple pair of programs communicating with each other through sockets; one which listens for a connection, accepts immediately, sends one single byte and exits right away. The other connecting to the port, sleeping one second, then reading one byte, then print that out and exit. That should demonstrate the differences between Unix and Windows without the overhead of all the Git stuff.

@dscho
Copy link

@dscho dscho commented on 48f7556 Mar 21, 2014

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, so I burnt up an hour of my Git time budget to come up with such a minimal example. However, I was not even able to get it to run without the sleep because for some reason, connect() always errors out. See my feeble attempt here.

If you have time to work on it, I would very much appreciate it. Otherwise I'll probably pick it up next week.

Please sign in to comment.