Simple IPC Mechanism
Subsystems (2)
cover Simple IPC Mechanism
This series introduces a multi-threaded IPC mechanism called "Simple IPC". This is a library-layer feature to make it easy to create very long running daemon/service applications and for unrelated Git commands to communicate with them. Communication uses pkt-line messaging over a Windows named pipe or Unix domain socket.
On the server side, Simple IPC implements a (platform-specific) connection listener and worker thread-pool to accept and handle a series of client connections. The server functionality is completely hidden behind the ipc_server_run() and ipc_server_run_async() APIs. The daemon/service application only needs to define an application-specific callback to handle client requests.
Note that Simple IPC is completely unrelated to the long running process feature (described in sub-process.h) where the lifetime of a "sub-process" child is bound to that of the invoking parent process and communication occurs over the child's stdin/stdout.
Simple IPC will serve as a basis for a future builtin FSMonitor daemon feature.
Jeff Hostetler (7):
pkt-line: use stack rather than static buffer in packet_write_gently()
simple-ipc: design documentation for new IPC mechanism
simple-ipc: add win32 implementation
unix-socket: create gentle version of unix_stream_listen()
unix-socket: add no-chdir option to unix_stream_listen_gently()
simple-ipc: add t/helper/test-simple-ipc and t0052
simple-ipc: add Unix domain socket implementation
Johannes Schindelin (3):
pkt-line: (optionally) libify the packet readers
pkt-line: optionally skip the flush packet in
write_packetized_from_buf()
pkt-line: accept additional options in read_packetized_to_strbuf()
Documentation/technical/api-simple-ipc.txt | 31 + Makefile | 8 + compat/simple-ipc/ipc-shared.c | 28 + compat/simple-ipc/ipc-unix-socket.c | 1093 ++++++++++++++++++++ compat/simple-ipc/ipc-win32.c | 723 +++++++++++++ config.mak.uname | 2 + contrib/buildsystems/CMakeLists.txt | 6 + convert.c | 4 +- pkt-line.c | 30 +- pkt-line.h | 13 +- simple-ipc.h | 221 ++++ t/helper/test-simple-ipc.c | 485 +++++++++ t/helper/test-tool.c | 1 + t/helper/test-tool.h | 1 + t/t0052-simple-ipc.sh | 129 +++ unix-socket.c | 58 +- unix-socket.h | 9 + 17 files changed, 2828 insertions(+), 14 deletions(-) create mode 100644 Documentation/technical/api-simple-ipc.txt create mode 100644 compat/simple-ipc/ipc-shared.c create mode 100644 compat/simple-ipc/ipc-unix-socket.c create mode 100644 compat/simple-ipc/ipc-win32.c create mode 100644 simple-ipc.h create mode 100644 t/helper/test-simple-ipc.c create mode 100755 t/t0052-simple-ipc.sh
base-commit: 71ca53e8125e36efbda17293c50027d31681a41f
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-766%2Fjeffhostetler%2Fsimple-ipc-v1
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-766/jeffhostetler/simple-ipc-v1
Pull-Request: https://github.com/gitgitgadget/git/pull/766
Signature · 1 line
gitgitgadget
PATCH 1/10 pkt-line: use stack rather than static buffer in packet_write_gently()
From: Jeff Hostetler <[email protected]>
Teach packet_write_gently() to use a stack buffer rather than a static buffer when composing the packet line message. This helps get us ready for threaded operations.
--- pkt-line.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
pkt-line.c +1 -1
@@ -196,7 +196,7 @@ int packet_write_fmt_gently(int fd, const char *fmt, ...)static int packet_write_gently (const int fd_out,const char *buf,size_t size) { -static char packet_write_buffer[LARGE_PACKET_MAX ]; +char packet_write_buffer[LARGE_PACKET_MAX ];size_t packet_size;if (size >sizeof (packet_write_buffer) -4 )
Signature · 1 line
gitgitgadget
PATCH 2/10 pkt-line: (optionally) libify the packet readers
In reply to Jeff Hostetler via GitGitGadgetFrom: Johannes Schindelin <[email protected]>
So far, the (possibly indirect) callers of get_packet_data() can ask that function to return an error instead of die()ing upon end-of-file. However, random read errors will still cause the process to die.
So let's introduce an explicit option to tell the packet reader machinery to please be nice and only return an error.
This change prepares pkt-line for use by long-running daemon processes. Such processes should be able to serve multiple concurrent clients and and survive random IO errors. If there is an error on one connection, a daemon should be able to drop that connection and continue serving existing and future connections.
This ability will be used by a Git-aware "Internal FSMonitor" feature in a later patch series.
--- pkt-line.c | 19 +++++++++++++++++-- pkt-line.h | 4 ++++ 2 files changed, 21 insertions(+), 2 deletions(-)
pkt-line.c +17 -2
@@ -298,8 +298,11 @@ static int get_packet_data(int fd, char **src_buf, size_t *src_size, *src_size -= ret; }else { ret =read_in_full (fd, dst, size); -if (ret <0 ) +if (ret <0 ) { +if (options &PACKET_READ_NEVER_DIE ) +return error_errno (_ ("read error" ));die_errno (_ ("read error" )); + } }/* And complain if we didn't get enough bytes to satisfy the read. */
@@ -307,6 +310,8 @@ static int get_packet_data(int fd, char **src_buf, size_t *src_size,if (options &PACKET_READ_GENTLE_ON_EOF )return -1 ; +if (options &PACKET_READ_NEVER_DIE ) +return error (_ ("the remote end hung up unexpectedly" ));die (_ ("the remote end hung up unexpectedly" )); }
@@ -335,6 +340,9 @@ enum packet_read_status packet_read_with_status(int fd, char **src_buffer, len =packet_length (linelen);if (len <0 ) { +if (options &PACKET_READ_NEVER_DIE ) +return error (_ ("protocol error: bad line length " +"character: %.4s" ), linelen);die (_ ("protocol error: bad line length character: %.4s" ), linelen); }else if (!len) {packet_trace ("0000" ,4 ,0 );
@@ -349,12 +357,19 @@ enum packet_read_status packet_read_with_status(int fd, char **src_buffer, *pktlen =0 ;return PACKET_READ_RESPONSE_END ; }else if (len <4 ) { +if (options &PACKET_READ_NEVER_DIE ) +return error (_ ("protocol error: bad line length %d" ), + len);die (_ ("protocol error: bad line length %d" ), len); } len -=4 ; -if ((unsigned )len >= size) +if ((unsigned )len >= size) { +if (options &PACKET_READ_NEVER_DIE ) +return error (_ ("protocol error: bad line length %d" ), + len);die (_ ("protocol error: bad line length %d" ), len); + }if (get_packet_data (fd, src_buffer, src_len, buffer, len, options) <0 ) { *pktlen =-1 ;
pkt-line.h +4 -0
@@ -68,10 +68,14 @@ int write_packetized_from_buf(const char *src_in, size_t len, int fd_out);* * If options contains PACKET_READ_DIE_ON_ERR_PACKET, it dies when it sees an * ERR packet. +* +* With `PACKET_READ_NEVER_DIE`, no errors are allowed to trigger die() (except +* an ERR packet, when `PACKET_READ_DIE_ON_ERR_PACKET` is in effect). */ #define PACKET_READ_GENTLE_ON_EOF (1u<<0)#define PACKET_READ_CHOMP_NEWLINE (1u<<1)#define PACKET_READ_DIE_ON_ERR_PACKET (1u<<2) +#define PACKET_READ_NEVER_DIE (1u<<3)int packet_read (int fd,char **src_buffer,size_t *src_len,char *buffer,unsigned size,int options);
Signature · 1 line
gitgitgadget
PATCH 3/10 pkt-line: optionally skip the flush packet in write_packetized_from_buf()
In reply to Jeff Hostetler via GitGitGadgetFrom: Johannes Schindelin <[email protected]>
This function currently has only one caller: apply_multi_file_filter() in convert.c. That caller wants a flush packet to be written after writing the payload.
However, we are about to introduce a user that wants to write many packets before a final flush packet, so let's extend this function to prepare for that scenario.
--- convert.c | 2 +- pkt-line.c | 5 +++-- pkt-line.h | 3 ++- 3 files changed, 6 insertions(+), 4 deletions(-)
convert.c +1 -1
@@ -886,7 +886,7 @@ static int apply_multi_file_filter(const char *path, const char *src, size_t lenif (fd >=0 ) err =write_packetized_from_fd (fd, process->in);else - err =write_packetized_from_buf (src, len, process->in); + err =write_packetized_from_buf (src, len, process->in,1 );if (err) goto done;
pkt-line.c +3 -2
@@ -261,7 +261,8 @@ int write_packetized_from_fd(int fd_in, int fd_out)return err; } -int write_packetized_from_buf (const char *src_in,size_t len,int fd_out) +int write_packetized_from_buf (const char *src_in,size_t len,int fd_out, +int flush_at_end) {int err =0 ;size_t bytes_written =0 ;
@@ -277,7 +278,7 @@ int write_packetized_from_buf(const char *src_in, size_t len, int fd_out) err =packet_write_gently (fd_out, src_in + bytes_written, bytes_to_write); bytes_written += bytes_to_write; } -if (!err) +if (!err && flush_at_end) err =packet_flush_gently (fd_out);return err; }
pkt-line.h +2 -1
@@ -33,7 +33,8 @@ void packet_buf_write_len(struct strbuf *buf, const char *data, size_t len);int packet_flush_gently (int fd);int packet_write_fmt_gently (int fd,const char *fmt, ...) __attribute__((format (printf,2 ,3 )));int write_packetized_from_fd (int fd_in,int fd_out); -int write_packetized_from_buf (const char *src_in,size_t len,int fd_out); +int write_packetized_from_buf (const char *src_in,size_t len,int fd_out, +int flush_at_end);/* * Read a packetized line into the buffer, which must be at least size bytes
Signature · 1 line
gitgitgadget
PATCH 4/10 pkt-line: accept additional options in read_packetized_to_strbuf()
In reply to Jeff Hostetler via GitGitGadgetFrom: Johannes Schindelin <[email protected]>
The read_packetized_to_strbuf() function reads packets into a strbuf until a flush packet has been received. So far, it has only one caller:apply_multi_file_filter() in convert.c. This caller really only needs the PACKET_READ_GENTLE_ON_EOF option to be passed to packet_read() (which makes sense in the scenario where packets should be read until a flush packet is received).
We are about to introduce a caller that wants to pass other options through to packet_read(), so let's extend the function signature accordingly.
--- convert.c | 2 +- pkt-line.c | 4 ++-- pkt-line.h | 6 +++++- 3 files changed, 8 insertions(+), 4 deletions(-)
convert.c +1 -1
@@ -903,7 +903,7 @@ static int apply_multi_file_filter(const char *path, const char *src, size_t lenif (err) goto done; - err =read_packetized_to_strbuf (process->out, &nbuf) <0 ; + err =read_packetized_to_strbuf (process->out, &nbuf,0 ) <0 ;if (err) goto done;
pkt-line.c +2 -2
@@ -437,7 +437,7 @@ char *packet_read_line_buf(char **src, size_t *src_len, int *dst_len)return packet_read_line_generic (-1 , src, src_len, dst_len); } -ssize_t read_packetized_to_strbuf (int fd_in,struct strbuf *sb_out) +ssize_t read_packetized_to_strbuf (int fd_in,struct strbuf *sb_out,int options) {int packet_len;
@@ -453,7 +453,7 @@ ssize_t read_packetized_to_strbuf(int fd_in, struct strbuf *sb_out)* that there is already room for the extra byte. */ sb_out->buf + sb_out->len,LARGE_PACKET_DATA_MAX +1 , - PACKET_READ_GENTLE_ON_EOF); + options | PACKET_READ_GENTLE_ON_EOF);if (packet_len <=0 )break ; sb_out->len += packet_len;
pkt-line.h +5 -1
@@ -135,8 +135,12 @@ char *packet_read_line_buf(char **src_buf, size_t *src_len, int *size);/* * Reads a stream of variable sized packets until a flush packet is detected. +* +* The options are augmented by PACKET_READ_GENTLE_ON_EOF and passed to +* packet_read. */ -ssize_t read_packetized_to_strbuf (int fd_in,struct strbuf *sb_out); +ssize_t read_packetized_to_strbuf (int fd_in,struct strbuf *sb_out, +int options);/* * Receive multiplexed output stream over git native protocol.
Signature · 1 line
gitgitgadget
PATCH 5/10 simple-ipc: design documentation for new IPC mechanism
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Brief design documentation for new IPC mechanism allowing foreground Git client to talk with an existing daemon process at a known location using a named pipe or unix domain socket.
--- Documentation/technical/api-simple-ipc.txt | 31 ++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 Documentation/technical/api-simple-ipc.txt
Documentation/technical/api-simple-ipc.txt new file +31 -0
@@ -0,0 +1,31 @@ +simple-ipc API +============== + +The simple-ipc API is used to send an IPC message and response between +a (presumably) foreground Git client process to a background server or +daemon process. The server process must already be running. Multiple +client processes can simultaneously communicate with the server +process. + +Communication occurs over a named pipe on Windows and a Unix domain +socket on other platforms. Clients and the server rendezvous at a +previously agreed-to application-specific pathname (which is outside +the scope of this design). + +This IPC mechanism differs from the existing `sub-process.c` model +(Documentation/technical/long-running-process-protocol.txt) and used +by applications like Git-LFS because the server is assumed to be very +long running system service. In contrast, a "sub-process model process" +is started with the foreground process and exits when the foreground +process terminates. How the server is started is also outside the +scope of the IPC mechanism. + +The IPC protocol consists of a single request message from the client and +an optional request message from the server. For simplicity, pkt-line +routines are used to hide chunking and buffering concerns. Each side +terminates their message with a flush packet. +(Documentation/technical/protocol-common.txt) + +The actual format of the client and server messages is application +specific. The IPC layer transmits and receives an opaque buffer without +any concern for the content within.
Signature · 1 line
gitgitgadget
PATCH 10/10 simple-ipc: add Unix domain socket implementation
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Create Unix domain socket based implementation of "simple-ipc".
--- Makefile | 2 + compat/simple-ipc/ipc-unix-socket.c | 1093 +++++++++++++++++++++++++++ contrib/buildsystems/CMakeLists.txt | 2 + simple-ipc.h | 7 +- 4 files changed, 1103 insertions(+), 1 deletion(-) create mode 100644 compat/simple-ipc/ipc-unix-socket.c
Makefile +2 -0
@@ -1681,6 +1681,8 @@ ifdef NO_UNIX_SOCKETS BASIC_CFLAGS += -DNO_UNIX_SOCKETS else LIB_OBJS += unix-socket.o + LIB_OBJS += compat/simple-ipc/ipc-shared.o + LIB_OBJS += compat/simple-ipc/ipc-unix-socket.o endif ifdef USE_WIN32_IPC
compat/simple-ipc/ipc-unix-socket.c new file +1093 -0
1,093 lines not on this page · raw message
contrib/buildsystems/CMakeLists.txt +2 -0
@@ -248,6 +248,8 @@ endif() if(CMAKE_SYSTEM_NAME STREQUAL "Windows") list(APPEND compat_SOURCES compat/simple-ipc/ipc-shared.c compat/simple-ipc/ipc-win32.c) +else() + list(APPEND compat_SOURCES compat/simple-ipc/ipc-shared.c compat/simple-ipc/ipc-unix-socket.c) endif() set(EXE_EXTENSION ${CMAKE_EXECUTABLE_SUFFIX})
simple-ipc.h +6 -1
@@ -5,7 +5,7 @@* See Documentation/technical/api-simple-ipc.txt */ -#if defined(GIT_WINDOWS_NATIVE ) +#if defined(GIT_WINDOWS_NATIVE ) || !defined(NO_UNIX_SOCKETS )#define SUPPORTS_SIMPLE_IPC #endif
@@ -151,6 +151,11 @@ struct ipc_server_data;struct ipc_server_opts {int nr_threads; + +/* +* Disallow chdir() when creating a Unix domain socket. +*/ +unsigned int uds_disallow_chdir:1 ; };/*
Signature · 1 line
gitgitgadget
PATCH 8/10 unix-socket: add no-chdir option to unix_stream_listen_gently()
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Calls to chdir() are dangerous in a multi-threaded context. If unix_stream_listen() is given a socket pathname that is too big to fit in a sockaddr_un structure, it will chdir() to the parent directory of the requested socket pathname, create the socket using a relative pathname, and then chdir() back. This is not thread-safe.
Add disallow_chdir flag to struct unix_sockaddr_context and change all callers to pass an initialized context structure.
Teach unix_sockaddr_init() to not allow calls to chdir() when flag is set.
Extend the public interface to unix_stream_listen_gently() to also expose this new flag.
--- unix-socket.c | 21 +++++++++++++++++---- unix-socket.h | 1 + 2 files changed, 18 insertions(+), 4 deletions(-)
unix-socket.c +17 -4
@@ -19,8 +19,15 @@ static int chdir_len(const char *orig, int len)struct unix_sockaddr_context {char *orig_dir; +unsigned int disallow_chdir:1 ; }; +#define UNIX_SOCKADDR_CONTEXT_INIT \ +{ \ + .orig_dir=NULL, \ + .disallow_chdir=0, \ +} +static void unix_sockaddr_cleanup (struct unix_sockaddr_context *ctx) {if (!ctx->orig_dir)
@@ -40,7 +47,11 @@ static int unix_sockaddr_init(struct sockaddr_un *sa, const char *path, {int size =strlen (path) +1 ; - ctx->orig_dir =NULL ; +if (ctx->disallow_chdir && size >sizeof (sa->sun_path)) { + errno =ENAMETOOLONG ; +return -1 ; + } +if (size >sizeof (sa->sun_path)) {const char *slash =find_last_dir_sep (path);const char *dir;
@@ -75,7 +86,7 @@ int unix_stream_connect(const char *path) {int fd, saved_errno;struct sockaddr_un sa; -struct unix_sockaddr_context ctx; +struct unix_sockaddr_context ctx =UNIX_SOCKADDR_CONTEXT_INIT ;if (unix_sockaddr_init (&sa, path, &ctx) <0 )return -1 ;
@@ -97,7 +108,7 @@ int unix_stream_listen(const char *path) {int fd, saved_errno;struct sockaddr_un sa; -struct unix_sockaddr_context ctx; +struct unix_sockaddr_context ctx =UNIX_SOCKADDR_CONTEXT_INIT ;unlink (path);
@@ -129,7 +140,9 @@ int unix_stream_listen_gently(const char *path,int bind_successful =0 ;int saved_errno;struct sockaddr_un sa; -struct unix_sockaddr_context ctx; +struct unix_sockaddr_context ctx =UNIX_SOCKADDR_CONTEXT_INIT ; + + ctx.disallow_chdir = opts->disallow_chdir;if (unix_sockaddr_init (&sa, path, &ctx) <0 ) goto fail;
unix-socket.h +1 -0
@@ -7,6 +7,7 @@ int unix_stream_listen(const char *path);struct unix_stream_listen_opts {int listen_backlog_size;unsigned int force_unlink_before_bind:1 ; +unsigned int disallow_chdir:1 ; }; int unix_stream_listen_gently(const char *path,
Signature · 1 line
gitgitgadget
PATCH 9/10 simple-ipc: add t/helper/test-simple-ipc and t0052
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Create unit tests for "simple-ipc". These are currently only enabled on Windows.
--- Makefile | 1 + t/helper/test-simple-ipc.c | 485 +++++++++++++++++++++++++++++++++++++ t/helper/test-tool.c | 1 + t/helper/test-tool.h | 1 + t/t0052-simple-ipc.sh | 129 ++++++++++ 5 files changed, 617 insertions(+) create mode 100644 t/helper/test-simple-ipc.c create mode 100755 t/t0052-simple-ipc.sh
Makefile +1 -0
@@ -740,6 +740,7 @@ TEST_BUILTINS_OBJS += test-serve-v2.o TEST_BUILTINS_OBJS += test-sha1.o TEST_BUILTINS_OBJS += test-sha256.o TEST_BUILTINS_OBJS += test-sigchain.o +TEST_BUILTINS_OBJS += test-simple-ipc.o TEST_BUILTINS_OBJS += test-strcmp-offset.o TEST_BUILTINS_OBJS += test-string-list.o TEST_BUILTINS_OBJS += test-submodule-config.o
t/helper/test-simple-ipc.c new file +485 -0
485 lines not on this page · raw message
t/helper/test-tool.c +1 -0
@@ -64,6 +64,7 @@ static struct test_cmd cmds[] = { { "sha1", cmd__sha1 }, { "sha256", cmd__sha256 }, { "sigchain", cmd__sigchain }, + { "simple-ipc", cmd__simple_ipc }, { "strcmp-offset", cmd__strcmp_offset }, { "string-list", cmd__string_list }, { "submodule-config", cmd__submodule_config },
t/helper/test-tool.h +1 -0
@@ -54,6 +54,7 @@ int cmd__sha1(int argc, const char **argv);int cmd__oid_array (int argc,const char **argv);int cmd__sha256 (int argc,const char **argv);int cmd__sigchain (int argc,const char **argv); +int cmd__simple_ipc (int argc,const char **argv);int cmd__strcmp_offset (int argc,const char **argv);int cmd__string_list (int argc,const char **argv);int cmd__submodule_config (int argc,const char **argv);
t/t0052-simple-ipc.sh new file +129 -0
129 lines not on this page · raw message
Signature · 1 line
gitgitgadget
PATCH 6/10 simple-ipc: add win32 implementation
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Create Windows implementation of "simple-ipc" using named pipes.
--- Makefile | 5 + compat/simple-ipc/ipc-shared.c | 28 ++ compat/simple-ipc/ipc-win32.c | 723 ++++++++++++++++++++++++++++ config.mak.uname | 2 + contrib/buildsystems/CMakeLists.txt | 4 + simple-ipc.h | 216 +++++++++ 6 files changed, 978 insertions(+) create mode 100644 compat/simple-ipc/ipc-shared.c create mode 100644 compat/simple-ipc/ipc-win32.c create mode 100644 simple-ipc.h
Makefile +5 -0
@@ -1682,6 +1682,11 @@ else LIB_OBJS += unix-socket.o endif +ifdef USE_WIN32_IPC + LIB_OBJS += compat/simple-ipc/ipc-shared.o + LIB_OBJS += compat/simple-ipc/ipc-win32.o +endif + ifdef NO_ICONV BASIC_CFLAGS += -DNO_ICONV endif
compat/simple-ipc/ipc-shared.c new file +28 -0
@@ -0,0 +1,28 @@ +#include "cache.h" +#include "simple-ipc.h" +#include "strbuf.h" +#include "pkt-line.h" +#include "thread-utils.h" + +#ifdef SUPPORTS_SIMPLE_IPC + +int ipc_server_run (const char *path,const struct ipc_server_opts *opts, +ipc_server_application_cb *application_cb, +void *application_data) +{ +struct ipc_server_data *server_data =NULL ; +int ret; + + ret =ipc_server_run_async (&server_data, path, opts, + application_cb, application_data); +if (ret) +return ret; + + ret =ipc_server_await (server_data); + +ipc_server_free (server_data); + +return ret; +} + +#endif /* SUPPORTS_SIMPLE_IPC */
compat/simple-ipc/ipc-win32.c new file +723 -0
723 lines not on this page · raw message
config.mak.uname +2 -0
@@ -421,6 +421,7 @@ ifeq ($(uname_S),Windows) RUNTIME_PREFIX = YesPlease HAVE_WPGMPTR = YesWeDo NO_ST_BLOCKS_IN_STRUCT_STAT = YesPlease + USE_WIN32_IPC = YesPlease USE_WIN32_MMAP = YesPlease MMAP_PREVENTS_DELETE = UnfortunatelyYes # USE_NED_ALLOCATOR = YesPlease
@@ -597,6 +598,7 @@ ifneq (,$(findstring MINGW,$(uname_S))) RUNTIME_PREFIX = YesPlease HAVE_WPGMPTR = YesWeDo NO_ST_BLOCKS_IN_STRUCT_STAT = YesPlease + USE_WIN32_IPC = YesPlease USE_WIN32_MMAP = YesPlease MMAP_PREVENTS_DELETE = UnfortunatelyYes USE_NED_ALLOCATOR = YesPlease
contrib/buildsystems/CMakeLists.txt +4 -0
@@ -246,6 +246,10 @@ elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux") list(APPEND compat_SOURCES unix-socket.c) endif() +if(CMAKE_SYSTEM_NAME STREQUAL "Windows") + list(APPEND compat_SOURCES compat/simple-ipc/ipc-shared.c compat/simple-ipc/ipc-win32.c) +endif() + set(EXE_EXTENSION ${CMAKE_EXECUTABLE_SUFFIX}) #header checks
simple-ipc.h new file +216 -0
216 lines not on this page · raw message
Signature · 1 line
gitgitgadget
PATCH 7/10 unix-socket: create gentle version of unix_stream_listen()
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Create a gentle version of unix_stream_listen(). This version does not call die() if a socket-fd cannot be created and does not assume that it is safe to unlink() an existing socket-inode.
unix_stream_listen() uses unix_stream_socket() helper function to create the socket-fd. Avoid that helper because it calls die() on errors.
unix_stream_listen() always tries to unlink() the socket-path before calling bind(). If there is an existing server/daemon already bound and listening on that socket-path, our unlink() would have the effect of disassociating the existing server's bound-socket-fd from the socket-path without notifying the existing server. The existing server could continue to service existing connections (accepted-socket-fd's), but would not receive any futher new connections (since clients rendezvous via the socket-path). The existing server would effectively be offline but yet appear to be active.
Furthermore, unix_stream_listen() creates an opportunity for a brief race condition for connecting clients if they try to connect in the interval between the forced unlink() and the subsequent bind() (which recreates the socket-path that is bound to a new socket-fd in the current process).
--- unix-socket.c | 39 +++++++++++++++++++++++++++++++++++++++ unix-socket.h | 8 ++++++++ 2 files changed, 47 insertions(+)
unix-socket.c +39 -0
@@ -121,3 +121,42 @@ int unix_stream_listen(const char *path) errno = saved_errno;return -1 ; } + +int unix_stream_listen_gently (const char *path, +const struct unix_stream_listen_opts *opts) +{ +int fd =-1 ; +int bind_successful =0 ; +int saved_errno; +struct sockaddr_un sa; +struct unix_sockaddr_context ctx; + +if (unix_sockaddr_init (&sa, path, &ctx) <0 ) + goto fail; + + fd =socket (AF_UNIX ,SOCK_STREAM ,0 ); +if (fd <0 ) + goto fail; + +if (opts->force_unlink_before_bind) +unlink (path); + +if (bind (fd, (struct sockaddr *)&sa,sizeof (sa)) <0 ) + goto fail; + bind_successful =1 ; + +if (listen (fd, opts->listen_backlog_size) <0 ) + goto fail; + +unix_sockaddr_cleanup (&ctx); +return fd; + +fail: + saved_errno = errno; +unix_sockaddr_cleanup (&ctx); +close (fd); +if (bind_successful) +unlink (path); + errno = saved_errno; +return -1 ; +}
unix-socket.h +8 -0
@@ -4,4 +4,12 @@int unix_stream_connect (const char *path);int unix_stream_listen (const char *path); +struct unix_stream_listen_opts { +int listen_backlog_size; +unsigned int force_unlink_before_bind:1 ; +}; + +int unix_stream_listen_gently (const char *path, +const struct unix_stream_listen_opts *opts); +#endif /* UNIX_SOCKET_H */
Signature · 1 line
gitgitgadget
Re: [PATCH 05/10] simple-ipc: design documentation for new IPC mechanism
In reply to Jeff Hostetler via GitGitGadgetQuoted 26 lines · Jeff Hostetler via GitGitGadget
From: Jeff Hostetler <[email protected]>
Brief design documentation for new IPC mechanism allowing foreground Git client to talk with an existing daemon process at a known location using a named pipe or unix domain socket.
Signed-off-by Johannes Schindelin <[email protected]>Signed-off-by Jeff Hostetler <[email protected]>--- Documentation/technical/api-simple-ipc.txt | 31 ++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 Documentation/technical/api-simple-ipc.txtDocumentation/technical/api-simple-ipc.txt new file +7
-0@@ -0,0 +1,31 @@ +simple-ipc API +============== + +The simple-ipc API is used to send an IPC message and response between +a (presumably) foreground Git client process to a background server or +daemon process. The server process must already be running. Multiple +client processes can simultaneously communicate with the server
Documentation/technical/api-simple-ipc.txt line 17
Quoted 10 lines
Documentation/technical/api-simple-ipc.txt new file +10
-0+process. + +Communication occurs over a named pipe on Windows and a Unix domain +socket on other platforms. Clients and the server rendezvous at a +previously agreed-to application-specific pathname (which is outside +the scope of this design). + +This IPC mechanism differs from the existing `sub-process.c` model +(Documentation/technical/long-running-process-protocol.txt) and used +by applications like Git-LFS because the server is assumed to be very
s/to be very long running/to be a long running/, or at least "s/to be very/to be a very/.
Quoted 14 lines
Documentation/technical/api-simple-ipc.txt new file +14
-0+long running system service. In contrast, a "sub-process model process" +is started with the foreground process and exits when the foreground +process terminates. How the server is started is also outside the +scope of the IPC mechanism. + +The IPC protocol consists of a single request message from the client and +an optional request message from the server. For simplicity, pkt-line +routines are used to hide chunking and buffering concerns. Each side +terminates their message with a flush packet. +(Documentation/technical/protocol-common.txt) + +The actual format of the client and server messages is application +specific. The IPC layer transmits and receives an opaque buffer without +any concern for the content within.
Quoted 13 lines · Jeff Hostetler via GitGitGadget
This series introduces a multi-threaded IPC mechanism called "Simple IPC". This is a library-layer feature to make it easy to create very long running daemon/service applications and for unrelated Git commands to communicate with them. Communication uses pkt-line messaging over a Windows named pipe or Unix domain socket.
On the server side, Simple IPC implements a (platform-specific) connection listener and worker thread-pool to accept and handle a series of client connections. The server functionality is completely hidden behind the
ipc_server_run()andipc_server_run_async()APIs. The daemon/service application only needs to define an application-specific callback to handle client requests.
Quoted 7 lines
Note that Simple IPC is completely unrelated to the long running process feature (described in
sub-process.h) where the lifetime of a "sub-process" child is bound to that of the invoking parent process and communication occurs over the child's stdin/stdout.Simple IPC will serve as a basis for a future builtin FSMonitor daemon feature.
I only skimmed this so far. In the past we had a git-read-cache--daemon -> git-index-helper[1] -> watchman. The last iteration of that seems to be the [3] re-roll from Ben Peart in 2017. I used/tested that for a while and had some near-production use-cases of it.
How does this new series relate to that past work (if at all), and (not having re-read the old threads) were there reasons those old patch serieses weren't merged in that are addressed here, mitigated etc?
1. https://lore.kernel.org/git/[email protected]/
2. https://lore.kernel.org/git/[email protected]/
3. https://lore.kernel.org/git/[email protected]/
4. https://lore.kernel.org/git/[email protected]/
Quoted 29 lines · Ævar Arnfjörð Bjarmason
Quoted 13 lines · Jeff Hostetler via GitGitGadget
This series introduces a multi-threaded IPC mechanism called "Simple IPC". This is a library-layer feature to make it easy to create very long running daemon/service applications and for unrelated Git commands to communicate with them. Communication uses pkt-line messaging over a Windows named pipe or Unix domain socket.
On the server side, Simple IPC implements a (platform-specific) connection listener and worker thread-pool to accept and handle a series of client connections. The server functionality is completely hidden behind the
ipc_server_run()andipc_server_run_async()APIs. The daemon/service application only needs to define an application-specific callback to handle client requests.Quoted 7 lines
Note that Simple IPC is completely unrelated to the long running process feature (described in
sub-process.h) where the lifetime of a "sub-process" child is bound to that of the invoking parent process and communication occurs over the child's stdin/stdout.Simple IPC will serve as a basis for a future builtin FSMonitor daemon feature.
I only skimmed this so far. In the past we had a git-read-cache--daemon -> git-index-helper[1] -> watchman. The last iteration of that seems to be the [3] re-roll from Ben Peart in 2017. I used/tested that for a while and had some near-production use-cases of it.
Quoted 9 lines
How does this new series relate to that past work (if at all), and (not having re-read the old threads) were there reasons those old patch serieses weren't merged in that are addressed here, mitigated etc?
1. https://lore.kernel.org/git/[email protected]/
2. https://lore.kernel.org/git/[email protected]/
3. https://lore.kernel.org/git/[email protected]/
4. https://lore.kernel.org/git/[email protected]/
I'm starting with the model used by the existing FSMonitor feature that Ben Peart and Kevin Willford added to Git.
Item [3] looks to be an earlier draft of that effort. The idea there was to add the fsmonitor hook that could talk to a daemon like Watchman and quickly update the in-memory cache-entry flags without the need to lstat() and similarly update the untracked-cache. An index extension was added to remember the last fsmonitor response processed.
Currently in Git, we have a fsmonitor hook (usually a perl script) that talks to Watchman and translates the Watchman response back into something that the Git client can understand. This comes back as a list of files that have changed since some timestamp (or in V2, relative to some daemon-specific token).
Items [1,2] are not related to that. That was a different effort to quickly fetch a read-only copy of an already-parsed index via shared memory. In the last version I saw, there were 2 daemons. index-helper kept a fresh view of the index in shared memory and could give it to the Git client. The client could just mmap the pre-parsed index and avoid calling read_index(). Index-helper would drive Watchman to keep track of cache-entries as they changed and handle the lstat's.
I'm not familiar with [4] (and I only quickly scanned it). There are several ideas for finding slow spots while reading the index. I don't want to go into all of them, but several are obsolete now. They didn't contribute to the current effort.
The Simple IPC series (and a soon to be submitted fsmonitor--daemon series) are intended to be a follow on to FSMonitor effort that is currently in Git.
1. Build a git-native daemon to watch the file system and avoid needing a third-party tool. This doesn't preclude the use of Watchman, but having a builtin tool might simplify engineering support costs when deploying to a large team.
2. Use direct IPC between the Git command and the daemon to avoid the expense of the Hook API (which is expensive on Windows).
3. Make the daemon Git-aware. For example, it might want to pre-filter ignored files. (This might not be present in V1. And we might extend the daemon to do more of this as we improve performance.)
Jeff
Quoted 13 lines · Jeff Hostetler via GitGitGadget
This series introduces a multi-threaded IPC mechanism called "Simple IPC". This is a library-layer feature to make it easy to create very long running daemon/service applications and for unrelated Git commands to communicate with them. Communication uses pkt-line messaging over a Windows named pipe or Unix domain socket.
On the server side, Simple IPC implements a (platform-specific) connection listener and worker thread-pool to accept and handle a series of client connections. The server functionality is completely hidden behind the
ipc_server_run()andipc_server_run_async()APIs. The daemon/service application only needs to define an application-specific callback to handle client requests.
Quoted 7 lines
Note that Simple IPC is completely unrelated to the long running process feature (described in
sub-process.h) where the lifetime of a "sub-process" child is bound to that of the invoking parent process and communication occurs over the child's stdin/stdout.Simple IPC will serve as a basis for a future builtin FSMonitor daemon feature.
What kind of security implications does this bring into the system?
Can a Simple IPC daemon be connected by any client? How does the daemon know that the other side is authorized to make requests? When a git binary acting as client connect to whatever happens to be listening to the well-known location, how does it know if the other side is the daemon it wanted to talk to and not a malicious MITM or other impersonator?
Or is this to be only used for "this is meant to be used to give read-only access to data that is public anyway" kind of daemon, e.g. "git://" transport that serves clones and fetches?
Or is this meant to be used on client machines where all the processes are assumed to be working for the end user, so it is OK to declare that anything will go (good old DOS mental model?)
I know at the Mechanism level we do not yet know how it will be used, but we cannot retrofit sufficient security, so it would be necessary to know answers to these questions.
Thanks.
Quoted 40 lines · Junio C Hamano
Quoted 13 lines · Jeff Hostetler via GitGitGadget
This series introduces a multi-threaded IPC mechanism called "Simple IPC". This is a library-layer feature to make it easy to create very long running daemon/service applications and for unrelated Git commands to communicate with them. Communication uses pkt-line messaging over a Windows named pipe or Unix domain socket.
On the server side, Simple IPC implements a (platform-specific) connection listener and worker thread-pool to accept and handle a series of client connections. The server functionality is completely hidden behind the
ipc_server_run()andipc_server_run_async()APIs. The daemon/service application only needs to define an application-specific callback to handle client requests.Quoted 7 lines
Note that Simple IPC is completely unrelated to the long running process feature (described in
sub-process.h) where the lifetime of a "sub-process" child is bound to that of the invoking parent process and communication occurs over the child's stdin/stdout.Simple IPC will serve as a basis for a future builtin FSMonitor daemon feature.
What kind of security implications does this bring into the system?
Can a Simple IPC daemon be connected by any client? How does the daemon know that the other side is authorized to make requests? When a git binary acting as client connect to whatever happens to be listening to the well-known location, how does it know if the other side is the daemon it wanted to talk to and not a malicious MITM or other impersonator?
Or is this to be only used for "this is meant to be used to give read-only access to data that is public anyway" kind of daemon, e.g. "
git://" transport that serves clones and fetches?Or is this meant to be used on client machines where all the processes are assumed to be working for the end user, so it is OK to declare that anything will go (good old DOS mental model?)
Quoted 6 lines
I know at the Mechanism level we do not yet know how it will be used, but we cannot retrofit sufficient security, so it would be necessary to know answers to these questions.
Thanks.
Good questions.
Yes, this is a local-only mechanism. A local-only named pipe on Windows and a Unix domain socket on Unix. In both cases the daemon creates the pipe/socket as the foreground user (since the daemon process will be implicitly started by the first Git command that needs to talk to it). Later client process try to open the pipe/socket with RW access if they can.
On Windows a local named pipe is created by the server side. It rejects
remote connections. I did not put an ACL, so it should inherit the
system default which grants the user RW access (since the daemon is
implicitly started by the first foreground client command that needs
to talk to it.) Other users in the user's group and the anonymous
user should have R but not W access to it, so they could not be able
to connect. The name pipe is kept in the local Named Pipe File System
(NPFS) as \\.\pipe\<unique-path> so it is globally visible on the system, but I don't think it is a problem.
On the Unix side, the socket is created inside the .git directory by the daemon. Potential clients would have to have access to the working directory and the .git directory to connect to the socket, so in normal circumstances they would be able to read everything in the WD anyway. So again, I don't think it is a problem.
Alternatively, if a malicious server is started and holds the named pipe or socket: the client might be able to talk to it (assuming that bad server grants access or impersonates the rightful user). The client might not be able to tell they've been tricked, but at that point the system is already compromised.
So unless I'm missing something, I think we're OK either way.
Jeff
Quoted 12 lines · Junio C Hamano
Quoted 9 lines · Jeff Hostetler
On Windows a local named pipe is created by the server side. It rejects remote connections. I did not put an ACL, so it should inherit the system default which grants the user RW access (since the daemon is implicitly started by the first foreground client command that needs to talk to it.) Other users in the user's group and the anonymous user should have R but not W access to it, so they could not be able to connect. The name pipe is kept in the local Named Pipe File System (NPFS) as
\\.\pipe\<unique-path>so it is globally visible on the system, but I don't think it is a problem.
Quoted 2 lines
It is not intuitively obvious why globalluy visible thing is OK to me, but I'll take your word for it on stuff about Windows.
Sorry, that's a quirk of Windows. Windows has a funky virtual drive where named pipes are stored -- kind of like a magic directory in /proc on Linux. All local named pipes have the "\\.\pipe\" path prefix.
So they are globally visible as a side-effect of that "namespace" restriction.
Quoted 14 lines
Quoted 6 lines
On the Unix side, the socket is created inside the .git directory by the daemon. Potential clients would have to have access to the working directory and the .git directory to connect to the socket, so in normal circumstances they would be able to read everything in the WD anyway. So again, I don't think it is a problem.
OK, yes, writability to .git would automatically mean that everything is a fair game to those who can talk to the daemon, so there is no new issue here, as long as the first process that creates the socket is careful not to loosen the permission.
Thanks.
Quoted 9 lines · Jeff Hostetler
On Windows a local named pipe is created by the server side. It rejects remote connections. I did not put an ACL, so it should inherit the system default which grants the user RW access (since the daemon is implicitly started by the first foreground client command that needs to talk to it.) Other users in the user's group and the anonymous user should have R but not W access to it, so they could not be able to connect. The name pipe is kept in the local Named Pipe File System (NPFS) as
\\.\pipe\<unique-path>so it is globally visible on the system, but I don't think it is a problem.
It is not intuitively obvious why globalluy visible thing is OK to me, but I'll take your word for it on stuff about Windows.
Quoted 5 lines
On the Unix side, the socket is created inside the .git directory by the daemon. Potential clients would have to have access to the working directory and the .git directory to connect to the socket, so in normal circumstances they would be able to read everything in the WD anyway. So again, I don't think it is a problem.
OK, yes, writability to .git would automatically mean that everything is a fair game to those who can talk to the daemon, so there is no new issue here, as long as the first process that creates the socket is careful not to loosen the permission.
Thanks.
Re: [PATCH 01/10] pkt-line: use stack rather than static buffer in packet_write_gently()
In reply to Jeff Hostetler via GitGitGadgetQuoted 3 lines · Jeff Hostetler via GitGitGadget
Teach
packet_write_gently()to use a stack buffer rather than a static buffer when composing the packet line message. This helps get us ready for threaded operations.
Sounds like a good goal, but...
pkt-line.c int packet_write_fmt_gently(int fd, const char *fmt, ...) · line 200
Quoted 5 lines
static int
packet_write_gently(const int fd_out, const char *buf, size_t size){pkt-line.c +1
-1-static char packet_write_buffer[LARGE_PACKET_MAX ]; +char packet_write_buffer[LARGE_PACKET_MAX ];size_t packet_size;
64k is awfully big for the stack, especially if you are thinking about having threads. I know we've run into issues around that size before (though I don't offhand recall whether there was any recursion involved).
We might need to use thread-local storage here. Heap would also obviously work, but I don't think we'd want a new allocation per write (or maybe it wouldn't matter; we're making a syscall, so a malloc() may not be that big a deal in terms of performance).
-Peff
Quoted 5 lines · Jeff Hostetler
On the Unix side, the socket is created inside the .git directory by the daemon. Potential clients would have to have access to the working directory and the .git directory to connect to the socket, so in normal circumstances they would be able to read everything in the WD anyway. So again, I don't think it is a problem.
Just thinking out loud, here are two potential issues with putting it in .git that we may have to deal with later:
- fsmonitor is conceptually a read-only thing (i.e., it would speed up "git status", etc). And not knowing much about how it will work, I'd guess that is carried through (i.e., even though you may open the socket R/W so that you can write requests and read them back, there is no operation you can request that will overwrite data). But the running user may not have write access to .git.
As long as we cleanly bail to the non-fsmonitor code paths, I don't think it's the end of the world. Those read-only users just won't get to use the speedup (and it may even be desirable). They may complain, but it is open source so the onus is on them to improve it. You will not have made anything worse. :)
- repositories may be on network filesystems that do not support unix sockets.
So it would be nice if there was some way to specify an alternate path to be used for the socket. Possibly one or both of:
- a config option to give a root path for sockets, where Git would then canonicalize the $GIT_DIR name and use $root/$GIT_DIR for the socket. That solves the problem for a given user once for all repos.
- a config option to say "use this path for the socket". This would be per-repo, but is more flexible and possibly less confusing.
One final note: on some systems[1] the permissions on the socket file itself are ignored. The safe way to protect it is to make sure the permissions on the surrounding directory are what you want. See credential-cache's init_socket_directory() for an example.
-Peff
[1] Sorry, I don't remember which systems. This is one of those random bits of Unix lore I've carried around for 20 years, and it's entirely possible it is simply obsolete at this point.
Re: [PATCH 07/10] unix-socket: create gentle version of unix_stream_listen()
In reply to Jeff Hostetler via GitGitGadgetQuoted 5 lines · Jeff Hostetler via GitGitGadget
From: Jeff Hostetler <[email protected]>
Create a gentle version of
unix_stream_listen(). This version does not calldie()if a socket-fd cannot be created and does not assume that it is safe tounlink()an existing socket-inode.
The existing one is meant to be gentle. Maybe it is worth fixing it instead.
Quoted 3 lines
unix_stream_listen()usesunix_stream_socket()helper function to create the socket-fd. Avoid that helper because it callsdie()on errors.
Yeah, I think this is just a bug. My thinking in the original was that socket() would basically never fail. And it generally wouldn't, but things like EMFILE do happen. There are only two callers, and both would be one-liners to propagate the error up the stack.
Quoted 9 lines
unix_stream_listen()always tries tounlink()the socket-path before callingbind(). If there is an existing server/daemon already bound and listening on that socket-path, ourunlink()would have the effect of disassociating the existing server's bound-socket-fd from the socket-path without notifying the existing server. The existing server could continue to service existing connections (accepted-socket-fd's), but would not receive any futher new connections (since clients rendezvous via the socket-path). The existing server would effectively be offline but yet appear to be active.
The trouble here is that one cannot tell if the existing file is active, and you are orphaning an existing server, or if there is leftover cruft from an exited server that did not clean up after itself (you will get EADDRINUSE either way).
Handling those cases (and especially doing so in a non-racy way) is probably outside the scope of unix_stream_listen(), but it makes sense for this to be an option. And it looks like you even made it so here, so unix_stream_listen() could just become a wrapper that sets the option. Or since there is only one caller in the whole code-base, perhaps it could just learn to pass the option struct. :)
Likewise for the no-chdir option added in the follow-on patch.
Quoted 5 lines
Furthermore,
unix_stream_listen()creates an opportunity for a brief race condition for connecting clients if they try to connect in the interval between the forcedunlink()and the subsequentbind()(which recreates the socket-path that is bound to a new socket-fd in the current process).
I'll be curious to see how you do this atomically. From my skim of patch 10, you will connect to see if it's active, and unlink if it's not. But then two simultaneous new processes could both see an inactive one and race to forcefully create the new one. One of them will lose and be orphaned with a socket that has no filesystem name.
There might be a solution using link() to have an atomic winner, but it gets tricky around unlinking the old name out of the way. You might need a separate dot-lock to make sure only one process does the unlink-and-create process at a time.
-Peff
Quoted 42 lines · Jeff King
Quoted 5 lines · Jeff Hostetler
On the Unix side, the socket is created inside the .git directory by the daemon. Potential clients would have to have access to the working directory and the .git directory to connect to the socket, so in normal circumstances they would be able to read everything in the WD anyway. So again, I don't think it is a problem.
Just thinking out loud, here are two potential issues with putting it in .git that we may have to deal with later:
- fsmonitor is conceptually a read-only thing (i.e., it would speed up "git status", etc). And not knowing much about how it will work, I'd guess that is carried through (i.e., even though you may open the socket R/W so that you can write requests and read them back, there is no operation you can request that will overwrite data). But the running user may not have write access to .git.
As long as we cleanly bail to the non-fsmonitor code paths, I don't think it's the end of the world. Those read-only users just won't get to use the speedup (and it may even be desirable). They may complain, but it is open source so the onus is on them to improve it. You will not have made anything worse. :)
- repositories may be on network filesystems that do not support unix sockets.
So it would be nice if there was some way to specify an alternate path to be used for the socket. Possibly one or both of:
- a config option to give a root path for sockets, where Git would then canonicalize the $GIT_DIR name and use $root/$GIT_DIR for the socket. That solves the problem for a given user once for all repos.
- a config option to say "use this path for the socket". This would be per-repo, but is more flexible and possibly less confusing.
One final note: on some systems[1] the permissions on the socket file itself are ignored. The safe way to protect it is to make sure the permissions on the surrounding directory are what you want. See credential-cache's
init_socket_directory()for an example.
Quoted 5 lines
-Peff
[1] Sorry, I don't remember which systems. This is one of those random bits of Unix lore I've carried around for 20 years, and it's entirely possible it is simply obsolete at this point.
According to StackExchange lore this seems to have been the case with 4.2 BSD & maybe something obscure like HP/UX:
https://unix.stackexchange.com/questions/83032/which-systems-do-not-honor-socket-read-write-permissions
I'd say it's probably safe to ignore this as a concern for new features in git in general, and certainly for something like a thing intended for a watchman-like program which users are likely to only run on modern OS's.
Re: [PATCH 07/10] unix-socket: create gentle version of unix_stream_listen()
In reply to Jeff KingI had saved this to comment on, but Peff beat me to it :-)
Quoted 2 lines · Jeff King
There might be a solution using
link()to have an atomic winner, but it gets tricky around unlinking the old name out of the way.
You definitely should be able to do this atomically with link(), but the cleanup is indeed messy, and there's already existing locking code, so it's probably better to press that into service here.
Chris
Re: [PATCH 01/10] pkt-line: use stack rather than static buffer in packet_write_gently()
In reply to Jeff KingQuoted 19 lines · Jeff King
Quoted 3 lines · Jeff Hostetler via GitGitGadget
Teach
packet_write_gently()to use a stack buffer rather than a static buffer when composing the packet line message. This helps get us ready for threaded operations.Sounds like a good goal, but...
pkt-line.c int packet_write_fmt_gently(int fd, const char *fmt, ...) · line 200
Quoted 5 lines
static int
packet_write_gently(const int fd_out, const char *buf, size_t size){pkt-line.c +1
-1-static char packet_write_buffer[LARGE_PACKET_MAX ]; +char packet_write_buffer[LARGE_PACKET_MAX ];size_t packet_size;64k is awfully big for the stack, especially if you are thinking about having threads. I know we've run into issues around that size before (though I don't offhand recall whether there was any recursion involved).
Quoted 7 lines
We might need to use thread-local storage here. Heap would also obviously work, but I don't think we'd want a new allocation per write (or maybe it wouldn't matter; we're making a syscall, so a
malloc()may not be that big a deal in terms of performance).-Peff
Good point.
I'll look at the callers and see if I can do something safer.
Jeeff
PATCH v2 1/14 ci/install-depends: attempt to fix "brew cask" stuff
In reply to Jeff Hostetler via GitGitGadgetFrom: Junio C Hamano <[email protected]>
We run "git pull" against "$cask_repo"; clarify that we are expecting not to have any of our own modifications and running "git pull" to merely update, by passing "--ff-only" on the command line.
Also, the "brew cask install" command line triggers an error message that says:
Error: Calling brew cask install is disabled! Use brew install [--cask] instead.
In addition, "brew install caskroom/cask/perforce" step triggers an error that says:
Error: caskroom/cask was moved. Tap homebrew/cask instead.
Attempt to see if blindly following the suggestion in these error messages gets us into a better shape.
--- ci/install-dependencies.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-)
ci/install-dependencies.sh +4 -4
@@ -44,13 +44,13 @@ osx-clang|osx-gcc) test -z "$BREW_INSTALL_PACKAGES" || brew install $BREW_INSTALL_PACKAGES brew link --force gettext - brew cask install --no-quarantine perforce || { + brew install --cask --no-quarantine perforce || { # Update the definitions and try again cask_repo="$(brew --repository)"/Library/Taps/homebrew/homebrew-cask && - git -C "$cask_repo" pull --no-stat && - brew cask install --no-quarantine perforce + git -C "$cask_repo" pull --no-stat --ff-only && + brew install --cask --no-quarantine perforce } || - brew install caskroom/cask/perforce + brew install homebrew/cask/perforce case "$jobname" in osx-gcc) brew install gcc@9
Signature · 1 line
gitgitgadget
v2 cover Simple IPC Mechanism
In reply to Jeff Hostetler via GitGitGadgetHere is version 2 of my "Simple IPC" series and addresses the following review comments:
[1] Redo packet_write_gently() to take a scratch buffer argument and fixup callers to avoid potential thread-stack problems caused by a very large stack buffer when used multi-threaded callers. This turned out to be a little more involved than anticipated because the pkt-line code doesn't know about its thread state nor an opportunity to initialize thread-state.
[2] Deleted the unix_stream_socket() helper function and inline it in the few callers and then let those call sites decide whether to call die() or not.
[3] Refactor unix_stream_listen() to take an "options" structure and to incorporate the changes I described in my earlier unix_stream_listen_gently().
[4] Update unix_stream_connect() to return errors rather than calling die().
[5] Update the simple-ipc server startup to detect dead and/or in-use Unix domain sockets and/or create a new socket in a race-friendly way. I now use a variation of the atomic lock-rename-trick when creating the socket (details are in a large comment in the code).
Jeff Hostetler (10):
pkt-line: promote static buffer in packet_write_gently() to callers
pkt-line: add write_packetized_from_buf2() that takes scratch buffer
simple-ipc: design documentation for new IPC mechanism
simple-ipc: add win32 implementation
simple-ipc: add t/helper/test-simple-ipc and t0052
unix-socket: elimiate static unix_stream_socket() helper function
unix-socket: add options to unix_stream_listen()
unix-socket: add no-chdir option to unix_stream_listen()
unix-socket: do not call die in unix_stream_connect()
simple-ipc: add Unix domain socket implementation
Johannes Schindelin (3):
pkt-line: optionally skip the flush packet in
write_packetized_from_buf()
pkt-line: (optionally) libify the packet readers
pkt-line: accept additional options in read_packetized_to_strbuf()
Junio C Hamano (1):
ci/install-depends: attempt to fix "brew cask" stuff
Documentation/technical/api-simple-ipc.txt | 34 + Makefile | 8 + builtin/credential-cache--daemon.c | 3 +- ci/install-dependencies.sh | 8 +- compat/simple-ipc/ipc-shared.c | 28 + compat/simple-ipc/ipc-unix-socket.c | 1127 ++++++++++++++++++++ compat/simple-ipc/ipc-win32.c | 751 +++++++++++++ config.mak.uname | 2 + contrib/buildsystems/CMakeLists.txt | 6 + convert.c | 4 +- pkt-line.c | 70 +- pkt-line.h | 26 +- simple-ipc.h | 230 ++++ t/helper/test-simple-ipc.c | 485 +++++++++ t/helper/test-tool.c | 1 + t/helper/test-tool.h | 1 + t/t0052-simple-ipc.sh | 129 +++ unix-socket.c | 67 +- unix-socket.h | 16 +- 19 files changed, 2949 insertions(+), 47 deletions(-) create mode 100644 Documentation/technical/api-simple-ipc.txt create mode 100644 compat/simple-ipc/ipc-shared.c create mode 100644 compat/simple-ipc/ipc-unix-socket.c create mode 100644 compat/simple-ipc/ipc-win32.c create mode 100644 simple-ipc.h create mode 100644 t/helper/test-simple-ipc.c create mode 100755 t/t0052-simple-ipc.sh
base-commit: 71ca53e8125e36efbda17293c50027d31681a41f
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-766%2Fjeffhostetler%2Fsimple-ipc-v2
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-766/jeffhostetler/simple-ipc-v2
Pull-Request: https://github.com/gitgitgadget/git/pull/766
Range-diff vs v1:
1: 1155a45cf64 < -: ----------- pkt-line: use stack rather than static buffer in packet_write_gently() -: ----------- > 1: 4c6766d4183 ci/install-depends: attempt to fix "brew cask" stuff -: ----------- > 2: 3b03a8ff7a7 pkt-line: promote static buffer in packet_write_gently() to callers -: ----------- > 3: e671894b4c0 pkt-line: add write_packetized_from_buf2() that takes scratch buffer 3: edf5ac95d66 ! 4: 0832f7d324d pkt-line: optionally skip the flush packet in write_packetized_from_buf()
@@ Commit message
packets before a final flush packet, so let's extend this function to prepare for that scenario.
+ Signed-off-by: Jeff Hostetler <[email protected]>
## convert.c ##
@@ pkt-line.c: int write_packetized_from_fd(int fd_in, int fd_out)
-int write_packetized_from_buf(const char *src_in, size_t len, int fd_out) +int write_packetized_from_buf(const char *src_in, size_t len, int fd_out,
+ int flush_at_end)
+ {
+ static struct packet_scratch_space scratch;
+
+- return write_packetized_from_buf2(src_in, len, fd_out, &scratch);
++ return write_packetized_from_buf2(src_in, len, fd_out,
++ flush_at_end, &scratch);
+ }
+
+ int write_packetized_from_buf2(const char *src_in, size_t len, int fd_out,
++ int flush_at_end,
+ struct packet_scratch_space *scratch)
{
int err = 0;
- size_t bytes_written = 0;
-@@ pkt-line.c: int write_packetized_from_buf(const char *src_in, size_t len, int fd_out)
- err = packet_write_gently(fd_out, src_in + bytes_written, bytes_to_write);
+@@ pkt-line.c: int write_packetized_from_buf2(const char *src_in, size_t len, int fd_out,
+ err = packet_write_gently(fd_out, src_in + bytes_written, bytes_to_write, scratch);
bytes_written += bytes_to_write;
}
- if (!err)
@@ pkt-line.h: void packet_buf_write_len(struct strbuf *buf, const char *data, size
-int write_packetized_from_buf(const char *src_in, size_t len, int fd_out); +int write_packetized_from_buf(const char *src_in, size_t len, int fd_out,
+ int flush_at_end);
+ int write_packetized_from_buf2(const char *src_in, size_t len, int fd_out,
++ int flush_at_end,
+ struct packet_scratch_space *scratch);
/*
- * Read a packetized line into the buffer, which must be at least size bytes
2: b7d678bc918 ! 5: 43bc4a26b79 pkt-line: (optionally) libify the packet readers
@@ pkt-line.c: enum packet_read_status packet_read_with_status(int fd, char **src_b
*pktlen = -1;
## pkt-line.h ##
-@@ pkt-line.h: int write_packetized_from_buf(const char *src_in, size_t len, int fd_out); +@@ pkt-line.h: int write_packetized_from_buf2(const char *src_in, size_t len, int fd_out,
*
* If options contains PACKET_READ_DIE_ON_ERR_PACKET, it dies when it sees an
* ERR packet.
4: 2f399ac107c = 6: 6a389a35335 pkt-line: accept additional options in read_packetized_to_strbuf()
5: 7064c5e9ffa ! 7: a7275b4bdc2 simple-ipc: design documentation for new IPC mechanism
@@ Documentation/technical/api-simple-ipc.txt (new)
+
+This IPC mechanism differs from the existing `sub-process.c` model
+(Documentation/technical/long-running-process-protocol.txt) and used
-+by applications like Git-LFS because the server is assumed to be very
-+long running system service. In contrast, a "sub-process model process"
-+is started with the foreground process and exits when the foreground
-+process terminates. How the server is started is also outside the
-+scope of the IPC mechanism.
++by applications like Git-LFS. In the simple-ipc model the server is
++assumed to be a very long-running system service. In contrast, in the
++LFS-style sub-process model the helper is started with the foreground
++process and exits when the foreground process terminates.
++
++How the simple-ipc server is started is also outside the scope of the
++IPC mechanism. For example, the server might be started during
++maintenance operations.
+
+The IPC protocol consists of a single request message from the client and
+an optional request message from the server. For simplicity, pkt-line
6: 9e27c07d785 ! 8: 388366913d4 simple-ipc: add win32 implementation
@@ compat/simple-ipc/ipc-win32.c (new)
+enum ipc_active_state ipc_client_try_connect(
+ const char *path,
+ const struct ipc_client_connect_options *options,
-+ int *pfd)
++ struct ipc_client_connection **p_connection)
+{
+ wchar_t wpath[MAX_PATH];
+ enum ipc_active_state state = IPC_STATE__OTHER_ERROR;
++ int fd = -1;
+
-+ *pfd = -1;
++ *p_connection = NULL;
+
+ trace2_region_enter("ipc-client", "try-connect", NULL);
+ trace2_data_string("ipc-client", NULL, "try-connect/path", path);
@@ compat/simple-ipc/ipc-win32.c (new)
+ state = IPC_STATE__INVALID_PATH;
+ else
+ state = connect_to_server(wpath, WINDOWS_CONNECTION_TIMEOUT_MS,
-+ options, pfd);
++ options, &fd);
+
+ trace2_data_intmax("ipc-client", NULL, "try-connect/state",
+ (intmax_t)state);
+ trace2_region_leave("ipc-client", "try-connect", NULL);
++
++ if (state == IPC_STATE__LISTENING) {
++ (*p_connection) = xcalloc(1, sizeof(struct ipc_client_connection));
++ (*p_connection)->fd = fd;
++ }
++
+ return state;
+}
+
-+int ipc_client_send_command_to_fd(int fd, const char *message,
-+ struct strbuf *answer)
++void ipc_client_close_connection(struct ipc_client_connection *connection)
++{
++ if (!connection)
++ return;
++
++ if (connection->fd != -1)
++ close(connection->fd);
++
++ free(connection);
++}
++
++int ipc_client_send_command_to_connection(
++ struct ipc_client_connection *connection,
++ const char *message, struct strbuf *answer)
+{
+ int ret = 0;
+
@@ compat/simple-ipc/ipc-win32.c (new)
+
+ trace2_region_enter("ipc-client", "send-command", NULL);
+
-+ if (write_packetized_from_buf(message, strlen(message), fd, 1) < 0) {
++ if (write_packetized_from_buf2(message, strlen(message),
++ connection->fd, 1,
++ &connection->scratch_write_buffer) < 0) {
+ ret = error(_("could not send IPC command"));
+ goto done;
+ }
+
-+ FlushFileBuffers((HANDLE)_get_osfhandle(fd));
++ FlushFileBuffers((HANDLE)_get_osfhandle(connection->fd));
+
-+ if (read_packetized_to_strbuf(fd, answer, PACKET_READ_NEVER_DIE) < 0) {
++ if (read_packetized_to_strbuf(connection->fd, answer,
++ PACKET_READ_NEVER_DIE) < 0) {
+ ret = error(_("could not read IPC response"));
+ goto done;
+ }
@@ compat/simple-ipc/ipc-win32.c (new)
+ const struct ipc_client_connect_options *options,
+ const char *message, struct strbuf *response)
+{
-+ int fd;
+ int ret = -1;
+ enum ipc_active_state state;
++ struct ipc_client_connection *connection = NULL;
+
-+ state = ipc_client_try_connect(path, options, &fd);
++ state = ipc_client_try_connect(path, options, &connection);
+
+ if (state != IPC_STATE__LISTENING)
+ return ret;
+
-+ ret = ipc_client_send_command_to_fd(fd, message, response);
-+ close(fd);
++ ret = ipc_client_send_command_to_connection(connection, message, response);
++
++ ipc_client_close_connection(connection);
++
+ return ret;
+}
+
@@ compat/simple-ipc/ipc-win32.c (new)
+ struct ipc_server_data *server_data;
+ pthread_t pthread_id;
+ HANDLE hPipe;
++ struct packet_scratch_space scratch_write_buffer;
+};
+
+/*
@@ compat/simple-ipc/ipc-win32.c (new)
+static int do_io_reply_callback(struct ipc_server_reply_data *reply_data,
+ const char *response, size_t response_len)
+{
++ struct packet_scratch_space *scratch =
++ &reply_data->server_thread_data->scratch_write_buffer;
++
+ if (reply_data->magic != MAGIC_SERVER_REPLY_DATA)
+ BUG("reply_cb called with wrong instance data");
+
-+ return write_packetized_from_buf(response, response_len,
-+ reply_data->fd, 0);
++ return write_packetized_from_buf2(response, response_len,
++ reply_data->fd, 0, scratch);
+}
+
+/*
@@ simple-ipc.h (new)
+#endif
+
+#ifdef SUPPORTS_SIMPLE_IPC
++#include "pkt-line.h"
+
+/*
+ * Simple IPC Client Side API.
@@ simple-ipc.h (new)
+ */
+enum ipc_active_state ipc_get_active_state(const char *path);
+
++struct ipc_client_connection {
++ int fd;
++ struct packet_scratch_space scratch_write_buffer;
++};
++
+/*
+ * Try to connect to the daemon on the named pipe or socket.
+ *
-+ * Returns IPC_STATE__LISTENING (and an fd) when connected.
++ * Returns IPC_STATE__LISTENING and a connection handle.
+ *
+ * Otherwise, returns info to help decide whether to retry or to
+ * spawn/respawn the server.
@@ simple-ipc.h (new)
+enum ipc_active_state ipc_client_try_connect(
+ const char *path,
+ const struct ipc_client_connect_options *options,
-+ int *pfd);
++ struct ipc_client_connection **p_connection);
++
++void ipc_client_close_connection(struct ipc_client_connection *connection);
+
+/*
+ * Used by the client to synchronously send and receive a message with
-+ * the server on the provided fd.
++ * the server on the provided client connection.
+ *
+ * Returns 0 when successful.
+ *
+ * Calls error() and returns non-zero otherwise.
+ */
-+int ipc_client_send_command_to_fd(int fd, const char *message,
-+ struct strbuf *answer);
++int ipc_client_send_command_to_connection(
++ struct ipc_client_connection *connection,
++ const char *message, struct strbuf *answer);
+
+/*
+ * Used by the client to synchronously connect and send and receive a
9: 69969c2b8d3 = 9: f0bebf1cdb3 simple-ipc: add t/helper/test-simple-ipc and t0052
-: ----------- > 10: f5d5445cf42 unix-socket: elimiate static unix_stream_socket() helper function
7: 96268351ac6 ! 11: 7a6a69dfc20 unix-socket: create gentle version of unix_stream_listen()
@@ Metadata
Author: Jeff Hostetler <[email protected]>
## Commit message ##
- unix-socket: create gentle version of unix_stream_listen()
+ unix-socket: add options to unix_stream_listen()
- Create a gentle version of `unix_stream_listen()`. This version does
- not call `die()` if a socket-fd cannot be created and does not assume
- that it is safe to `unlink()` an existing socket-inode.
+ Update `unix_stream_listen()` to take an options structure to override
+ default behaviors. This includes the size of the `listen()` backlog
+ and whether it should always unlink the socket file before trying to
+ create a new one. Also eliminate calls to `die()` if it cannot create
+ a socket.
- `unix_stream_listen()` uses `unix_stream_socket()` helper function to
- create the socket-fd. Avoid that helper because it calls `die()` on
- errors.
-
- `unix_stream_listen()` always tries to `unlink()` the socket-path before
- calling `bind()`. If there is an existing server/daemon already bound
- and listening on that socket-path, our `unlink()` would have the effect
- of disassociating the existing server's bound-socket-fd from the socket-path
- without notifying the existing server. The existing server could continue
- to service existing connections (accepted-socket-fd's), but would not
- receive any futher new connections (since clients rendezvous via the
- socket-path). The existing server would effectively be offline but yet
- appear to be active.
+ Normally, `unix_stream_listen()` always tries to `unlink()` the
+ socket-path before calling `bind()`. If there is an existing
+ server/daemon already bound and listening on that socket-path, our
+ `unlink()` would have the effect of disassociating the existing
+ server's bound-socket-fd from the socket-path without notifying the
+ existing server. The existing server could continue to service
+ existing connections (accepted-socket-fd's), but would not receive any
+ futher new connections (since clients rendezvous via the socket-path).
+ The existing server would effectively be offline but yet appear to be
+ active.
Furthermore, `unix_stream_listen()` creates an opportunity for a brief
race condition for connecting clients if they try to connect in the
@@ Commit message
Signed-off-by: Jeff Hostetler <[email protected]>
+ ## builtin/credential-cache--daemon.c ##
+@@ builtin/credential-cache--daemon.c: static int serve_cache_loop(int fd)
+
+ static void serve_cache(const char *socket_path, int debug)
+ {
++ struct unix_stream_listen_opts opts = UNIX_STREAM_LISTEN_OPTS_INIT;
+ int fd;
+
+- fd = unix_stream_listen(socket_path);
++ fd = unix_stream_listen(socket_path, &opts);
+ if (fd < 0)
+ die_errno("unable to bind to '%s'", socket_path);
+
+
## unix-socket.c ##
-@@ unix-socket.c: int unix_stream_listen(const char *path)
- errno = saved_errno;
+@@ unix-socket.c: int unix_stream_connect(const char *path)
return -1;
}
-+
-+int unix_stream_listen_gently(const char *path,
-+ const struct unix_stream_listen_opts *opts)
-+{
+
+-int unix_stream_listen(const char *path)
++int unix_stream_listen(const char *path,
++ const struct unix_stream_listen_opts *opts)
+ {
+- int fd, saved_errno;
+ int fd = -1;
-+ int bind_successful = 0;
+ int saved_errno;
-+ struct sockaddr_un sa;
-+ struct unix_sockaddr_context ctx;
-+
-+ if (unix_sockaddr_init(&sa, path, &ctx) < 0)
-+ goto fail;
++ int bind_successful = 0;
++ int backlog;
+ struct sockaddr_un sa;
+ struct unix_sockaddr_context ctx;
+
+- unlink(path);
+-
+ if (unix_sockaddr_init(&sa, path, &ctx) < 0)
+ return -1;
+
-+ fd = socket(AF_UNIX, SOCK_STREAM, 0);
-+ if (fd < 0)
+ fd = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (fd < 0)
+- die_errno("unable to create socket");
+ goto fail;
+
+ if (opts->force_unlink_before_bind)
+ unlink(path);
-+
-+ if (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) < 0)
-+ goto fail;
+
+ if (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) < 0)
+ goto fail;
+ bind_successful = 1;
-+
-+ if (listen(fd, opts->listen_backlog_size) < 0)
-+ goto fail;
-+
-+ unix_sockaddr_cleanup(&ctx);
-+ return fd;
-+
-+fail:
-+ saved_errno = errno;
-+ unix_sockaddr_cleanup(&ctx);
-+ close(fd);
+
+- if (listen(fd, 5) < 0)
++ if (opts->listen_backlog_size > 0)
++ backlog = opts->listen_backlog_size;
++ else
++ backlog = 5;
++ if (listen(fd, backlog) < 0)
+ goto fail;
+
+ unix_sockaddr_cleanup(&ctx);
+@@ unix-socket.c: int unix_stream_listen(const char *path)
+ fail:
+ saved_errno = errno;
+ unix_sockaddr_cleanup(&ctx);
+- close(fd);
++ if (fd != -1)
++ close(fd);
+ if (bind_successful)
+ unlink(path);
-+ errno = saved_errno;
-+ return -1;
-+}
+ errno = saved_errno;
+ return -1;
+ }
## unix-socket.h ##
@@
- int unix_stream_connect(const char *path);
- int unix_stream_listen(const char *path);
+ #ifndef UNIX_SOCKET_H
+ #define UNIX_SOCKET_H
+struct unix_stream_listen_opts {
+ int listen_backlog_size;
+ unsigned int force_unlink_before_bind:1;
+};
+
-+int unix_stream_listen_gently(const char *path,
-+ const struct unix_stream_listen_opts *opts);
++#define UNIX_STREAM_LISTEN_OPTS_INIT \
++{ \
++ .listen_backlog_size = 5, \
++ .force_unlink_before_bind = 1, \
++}
+
+ int unix_stream_connect(const char *path);
+-int unix_stream_listen(const char *path);
++int unix_stream_listen(const char *path,
++ const struct unix_stream_listen_opts *opts);
+
#endif /* UNIX_SOCKET_H */ 8: 383a9755669 ! 12: 745b6d5fb74 unix-socket: add no-chdir option to unix_stream_listen_gently()
@@ Metadata
Author: Jeff Hostetler <[email protected]>
## Commit message ##
- unix-socket: add no-chdir option to unix_stream_listen_gently()
+ unix-socket: add no-chdir option to unix_stream_listen()
Calls to chdir() are dangerous in a multi-threaded context. If unix_stream_listen() is given a socket pathname that is too big to
@@ Commit message
Teach unix_sockaddr_init() to not allow calls to chdir() when flag is set.
- Extend the public interface to unix_stream_listen_gently() to also
- expose this new flag.
-
## unix-socket.c ##
@@ unix-socket.c: int unix_stream_connect(const char *path)
if (unix_sockaddr_init(&sa, path, &ctx) < 0)
return -1;
-@@ unix-socket.c: int unix_stream_listen(const char *path)
- {
- int fd, saved_errno;
- struct sockaddr_un sa;
-- struct unix_sockaddr_context ctx;
-+ struct unix_sockaddr_context ctx = UNIX_SOCKADDR_CONTEXT_INIT;
-
- unlink(path);
-
-@@ unix-socket.c: int unix_stream_listen_gently(const char *path, +@@ unix-socket.c: int unix_stream_listen(const char *path,
int bind_successful = 0;
- int saved_errno;
+ int backlog;
struct sockaddr_un sa;
- struct unix_sockaddr_context ctx;
+ struct unix_sockaddr_context ctx = UNIX_SOCKADDR_CONTEXT_INIT;
@@ unix-socket.c: int unix_stream_listen_gently(const char *path,
+ ctx.disallow_chdir = opts->disallow_chdir;
if (unix_sockaddr_init(&sa, path, &ctx) < 0)
- goto fail;
+ return -1;
## unix-socket.h ##
-@@ unix-socket.h: int unix_stream_listen(const char *path); +@@
struct unix_stream_listen_opts {
int listen_backlog_size;
unsigned int force_unlink_before_bind:1;
+ unsigned int disallow_chdir:1;
};
- int unix_stream_listen_gently(const char *path,
+ #define UNIX_STREAM_LISTEN_OPTS_INIT \
+ { \
+ .listen_backlog_size = 5, \
+ .force_unlink_before_bind = 1, \
++ .disallow_chdir = 0, \
+ }
+
+ int unix_stream_connect(const char *path);
-: ----------- > 13: 2cca15a10ec unix-socket: do not call die in unix_stream_connect()
10: a1b15fb5cb0 ! 14: 72c1c209c38 simple-ipc: add Unix domain socket implementation
@@ Commit message
Create Unix domain socket based implementation of "simple-ipc".
+ A set of `ipc_client` routines implement a client library to connect
+ to an `ipc_server` over a Unix domain socket, send a simple request,
+ and receive a single response. Clients use blocking IO on the socket.
+
+ A set of `ipc_server` routines implement a thread pool to listen for
+ and concurrently service client connections.
+
+ The server creates a new Unix domain socket at a known location. If a
+ socket already exists with that name, the server tries to determine if
+ another server is already listening on the socket or if the socket is
+ dead. If socket is busy, the server exits with an error rather than
+ stealing the socket. If the socket is dead, the server creates a new
+ one and starts up.
+
+ If while running, the server detects that its socket has been stolen
+ by another server, it automatically exits.
+
Signed-off-by: Jeff Hostetler <[email protected]>
## Makefile ##
@@ compat/simple-ipc/ipc-unix-socket.c (new)
+ struct ipc_client_connect_options options
+ = IPC_CLIENT_CONNECT_OPTIONS_INIT;
+ struct stat st;
-+ int fd_test = -1;
++ struct ipc_client_connection *connection_test = NULL;
+
+ options.wait_if_busy = 0;
+ options.wait_if_not_found = 0;
@@ compat/simple-ipc/ipc-unix-socket.c (new)
+ * at `path`, doesn't mean it that there is a server listening.
+ * Ping it to be sure.
+ */
-+ state = ipc_client_try_connect(path, &options, &fd_test);
-+ close(fd_test);
++ state = ipc_client_try_connect(path, &options, &connection_test);
++ ipc_client_close_connection(connection_test);
+
+ return state;
+}
@@ compat/simple-ipc/ipc-unix-socket.c (new)
+enum ipc_active_state ipc_client_try_connect(
+ const char *path,
+ const struct ipc_client_connect_options *options,
-+ int *pfd)
++ struct ipc_client_connection **p_connection)
+{
+ enum ipc_active_state state = IPC_STATE__OTHER_ERROR;
++ int fd = -1;
+
-+ *pfd = -1;
++ *p_connection = NULL;
+
+ trace2_region_enter("ipc-client", "try-connect", NULL);
+ trace2_data_string("ipc-client", NULL, "try-connect/path", path);
+
+ state = connect_to_server(path, MY_CONNECTION_TIMEOUT_MS,
-+ options, pfd);
++ options, &fd);
+
+ trace2_data_intmax("ipc-client", NULL, "try-connect/state",
+ (intmax_t)state);
+ trace2_region_leave("ipc-client", "try-connect", NULL);
++
++ if (state == IPC_STATE__LISTENING) {
++ (*p_connection) = xcalloc(1, sizeof(struct ipc_client_connection));
++ (*p_connection)->fd = fd;
++ }
++
+ return state;
+}
+
-+int ipc_client_send_command_to_fd(int fd, const char *message,
-+ struct strbuf *answer)
++void ipc_client_close_connection(struct ipc_client_connection *connection)
++{
++ if (!connection)
++ return;
++
++ if (connection->fd != -1)
++ close(connection->fd);
++
++ free(connection);
++}
++
++int ipc_client_send_command_to_connection(
++ struct ipc_client_connection *connection,
++ const char *message, struct strbuf *answer)
+{
+ int ret = 0;
+
@@ compat/simple-ipc/ipc-unix-socket.c (new)
+
+ trace2_region_enter("ipc-client", "send-command", NULL);
+
-+ if (write_packetized_from_buf(message, strlen(message), fd, 1) < 0) {
++ if (write_packetized_from_buf2(message, strlen(message),
++ connection->fd, 1,
++ &connection->scratch_write_buffer) < 0) {
+ ret = error(_("could not send IPC command"));
+ goto done;
+ }
+
-+ if (read_packetized_to_strbuf(fd, answer, PACKET_READ_NEVER_DIE) < 0) {
++ if (read_packetized_to_strbuf(connection->fd, answer,
++ PACKET_READ_NEVER_DIE) < 0) {
+ ret = error(_("could not read IPC response"));
+ goto done;
+ }
@@ compat/simple-ipc/ipc-unix-socket.c (new)
+ const struct ipc_client_connect_options *options,
+ const char *message, struct strbuf *answer)
+{
-+ int fd;
+ int ret = -1;
+ enum ipc_active_state state;
++ struct ipc_client_connection *connection = NULL;
+
-+ state = ipc_client_try_connect(path, options, &fd);
++ state = ipc_client_try_connect(path, options, &connection);
+
+ if (state != IPC_STATE__LISTENING)
+ return ret;
+
-+ ret = ipc_client_send_command_to_fd(fd, message, answer);
-+ close(fd);
++ ret = ipc_client_send_command_to_connection(connection, message, answer);
++
++ ipc_client_close_connection(connection);
++
+ return ret;
+}
+
@@ compat/simple-ipc/ipc-unix-socket.c (new)
+ struct ipc_worker_thread_data *next_thread;
+ struct ipc_server_data *server_data;
+ pthread_t pthread_id;
++ struct packet_scratch_space scratch_write_buffer;
+};
+
+struct ipc_accept_thread_data {
+ enum magic magic;
+ struct ipc_server_data *server_data;
++
+ int fd_listen;
-+ ino_t inode_listen;
++ struct stat st_listen;
++
+ int fd_send_shutdown;
+ int fd_wait_shutdown;
+ pthread_t pthread_id;
@@ compat/simple-ipc/ipc-unix-socket.c (new)
+static int do_io_reply_callback(struct ipc_server_reply_data *reply_data,
+ const char *response, size_t response_len)
+{
++ struct packet_scratch_space *scratch =
++ &reply_data->worker_thread_data->scratch_write_buffer;
++
+ if (reply_data->magic != MAGIC_SERVER_REPLY_DATA)
+ BUG("reply_cb called with wrong instance data");
+
-+ return write_packetized_from_buf(response, response_len,
-+ reply_data->fd, 0);
++ return write_packetized_from_buf2(response, response_len,
++ reply_data->fd, 0, scratch);
+}
+
+/* A randomly chosen value. */
@@ compat/simple-ipc/ipc-unix-socket.c (new)
+static int socket_was_stolen(struct ipc_accept_thread_data *accept_thread_data)
+{
+ struct stat st;
++ struct stat *ref_st = &accept_thread_data->st_listen;
+
+ if (lstat(accept_thread_data->server_data->buf_path.buf, &st) == -1)
+ return 1;
+
-+ if (st.st_ino != accept_thread_data->inode_listen)
++ if (st.st_ino != ref_st->st_ino)
+ return 1;
+
++ /* We might also consider the creation time on some platforms. */
++
+ return 0;
+}
+
@@ compat/simple-ipc/ipc-unix-socket.c (new)
+ * open/connect using the "socket-inode" pathname.
+ *
+ * Unix domain sockets have a fundamental design flaw because the
-+ * "socket-inode" persists until the pathname is deleted; closing the listening
-+ * "socket-fd" only closes the socket handle/descriptor, it does not delete
-+ * the inode/pathname.
++ * "socket-inode" persists until the pathname is deleted; closing the
++ * listening "socket-fd" only closes the socket handle/descriptor, it
++ * does not delete the inode/pathname.
+ *
+ * Well-behaving service daemons are expected to also delete the inode
+ * before shutdown. If a service crashes (or forgets) it can leave
@@ compat/simple-ipc/ipc-unix-socket.c (new)
+ * inode *or* another service instance is already running.
+ *
+ * One possible solution is to blindly unlink the inode before
-+ * attempting to bind a new socket-fd (and thus create) a new
++ * attempting to bind a new socket-fd and thus create a new
+ * socket-inode. Then `bind(2)` should always succeed. However, if
-+ * there is an existing service instance, it would be orphaned --
-+ * it would still be listening on a socket-fd that is still bound
-+ * to an (unlinked) socket-inode, but that socket-inode is no longer
++ * there is an existing service instance, it would be orphaned -- it
++ * would still be listening on a socket-fd that is still bound to an
++ * (unlinked) socket-inode, but that socket-inode is no longer
+ * associated with the pathname. New client connections will arrive
-+ * at our new socket-inode and not the existing server's. (It is upto
-+ * the existing server to detect that its socket-inode has been
-+ * stolen and shutdown.)
++ * at OUR new socket-inode -- rather than the existing server's
++ * socket. (I suppose it is up to the existing server to detect that
++ * its socket-inode has been stolen and shutdown.)
++ *
++ * Another possible solution is to try to use the ".lock" trick, but
++ * bind() does not have a exclusive-create use bit like open() does,
++ * so we cannot have multiple servers fighting/racing to create the
++ * same file name without having losers lose without knowing that they
++ * lost.
++ *
++ * We try to avoid such stealing and would rather fail to run than
++ * steal an existing socket-inode (because we assume that the
++ * existing server has more context and value to the clients than a
++ * freshly started server). However, if multiple servers are racing
++ * to start, we don't care which one wins -- none of them have any
++ * state information yet worth fighting for.
++ *
++ * Create a "unique" socket-inode (with our PID in it (and assume that
++ * we can force-delete an existing socket with that name)). Stat it
++ * to get the inode number and ctime -- so that we can identify it as
++ * the one we created. Then use the atomic-rename trick to install it
++ * in the real location. (This will unlink an existing socket with
++ * that pathname -- and thereby steal the real socket-inode from an
++ * existing server.)
+ *
-+ * Since this is rather obscure and infrequent, we try to "gently"
-+ * create the socket-inode without disturbing an existing service.
++ * Elsewhere, our thread will periodically poll the socket-inode to
++ * see if someone else steals ours.
+ */
+static int create_listener_socket(const char *path,
-+ const struct ipc_server_opts *ipc_opts)
++ const struct ipc_server_opts *ipc_opts,
++ struct stat *st_socket)
+{
++ struct stat st;
++ struct strbuf buf_uniq = STRBUF_INIT;
+ int fd_listen;
-+ int fd_client;
-+ struct unix_stream_listen_opts uslg_opts = {
-+ .listen_backlog_size = LISTEN_BACKLOG,
-+ .force_unlink_before_bind = 0,
-+ .disallow_chdir = ipc_opts->uds_disallow_chdir
-+ };
-+
-+ trace2_data_string("ipc-server", NULL, "try-listen-gently", path);
-+
-+ /*
-+ * Assume socket-inode does not exist and try to (gently)
-+ * create a new socket-inode on disk at pathname and bind
-+ * socket-fd to it.
-+ */
-+ fd_listen = unix_stream_listen_gently(path, &uslg_opts);
-+ if (fd_listen >= 0)
-+ return fd_listen;
++ struct unix_stream_listen_opts uslg_opts = UNIX_STREAM_LISTEN_OPTS_INIT;
+
-+ if (errno != EADDRINUSE)
-+ return error_errno(_("could not create socket '%s'"),
-+ path);
-+
-+ trace2_data_string("ipc-server", NULL, "try-detect-server", path);
-+
-+ /*
-+ * A socket-inode at pathname exists on disk, but we don't
-+ * know if it a server is using it or if it is a stale inode.
-+ *
-+ * poke it with a trivial connection to try to find out.
-+ */
-+ fd_client = unix_stream_connect(path);
-+ if (fd_client >= 0) {
++ if (!lstat(path, &st) && S_ISSOCK(st.st_mode)) {
++ int fd_client;
+ /*
-+ * An existing service process is alive and accepted our
-+ * connection.
++ * A socket-inode at `path` exists on disk, but we
++ * don't know whether it belongs to an active server
++ * or if the last server died without cleaning up.
++ *
++ * Poke it with a trivial connection to try to find out.
+ */
-+ close(fd_client);
-+
-+ /*
-+ * We cannot create a new socket-inode here, so we cannot
-+ * startup a new server on this pathname.
-+ */
-+ errno = EADDRINUSE;
-+ return error_errno(_("socket already in use '%s'"),
++ trace2_data_string("ipc-server", NULL, "try-detect-server",
+ path);
++ fd_client = unix_stream_connect(path);
++ if (fd_client >= 0) {
++ close(fd_client);
++ errno = EADDRINUSE;
++ return error_errno(_("socket already in use '%s'"),
++ path);
++ }
+ }
+
-+ trace2_data_string("ipc-server", NULL, "try-listen-force", path);
-+
+ /*
-+ * A socket-inode at pathname exists on disk, but we were not
-+ * able to connect to it, so we believe that this is a stale
-+ * socket-inode that a previous server forgot to delete. Use
-+ * the tradional solution: force unlink it and create a new
-+ * one.
-+ *
-+ * TODO Note that it is possible that another server is
-+ * listening, but is either just starting up and not yet
-+ * responsive or is stuck somehow. For now, I'm OK with
-+ * stealing the socket-inode from it in this case.
++ * Create pathname to our "unique" socket and set it up for
++ * business.
+ */
-+ uslg_opts.force_unlink_before_bind = 1;
-+ fd_listen = unix_stream_listen_gently(path, &uslg_opts);
-+ if (fd_listen >= 0)
-+ return fd_listen;
-+
-+ return error_errno(_("could not force create socket '%s'"), path);
-+}
-+
-+static int setup_listener_socket(const char *path, ino_t *inode,
-+ const struct ipc_server_opts *ipc_opts)
-+{
-+ int fd_listen;
-+ struct stat st;
-+
-+ trace2_region_enter("ipc-server", "create-listener_socket", NULL);
-+ fd_listen = create_listener_socket(path, ipc_opts);
-+ trace2_region_leave("ipc-server", "create-listener_socket", NULL);
++ strbuf_addf(&buf_uniq, "%s.%d", path, getpid());
+
-+ if (fd_listen < 0)
-+ return fd_listen;
-+
-+ /*
-+ * We just bound a socket (descriptor) to a newly created unix
-+ * domain socket in the filesystem. Capture the inode number
-+ * so we can later detect if/when someone else force-creates a
-+ * new socket and effectively steals the path from us. (Which
-+ * would leave us listening to a socket that no client could
-+ * reach.)
-+ */
-+ if (lstat(path, &st) < 0) {
++ uslg_opts.listen_backlog_size = LISTEN_BACKLOG;
++ uslg_opts.force_unlink_before_bind = 1;
++ uslg_opts.disallow_chdir = ipc_opts->uds_disallow_chdir;
++ fd_listen = unix_stream_listen(buf_uniq.buf, &uslg_opts);
++ if (fd_listen < 0) {
+ int saved_errno = errno;
++ error_errno(_("could not create listener socket '%s'"),
++ buf_uniq.buf);
++ strbuf_release(&buf_uniq);
++ errno = saved_errno;
++ return -1;
++ }
+
++ if (lstat(buf_uniq.buf, st_socket)) {
++ int saved_errno = errno;
++ error_errno(_("could not stat listener socket '%s'"),
++ buf_uniq.buf);
+ close(fd_listen);
-+ unlink(path);
-+
++ unlink(buf_uniq.buf);
++ strbuf_release(&buf_uniq);
+ errno = saved_errno;
-+ return error_errno(_("could not lstat listener socket '%s'"),
-+ path);
++ return -1;
+ }
+
+ if (set_socket_blocking_flag(fd_listen, 1)) {
+ int saved_errno = errno;
-+
++ error_errno(_("could not set listener socket nonblocking '%s'"),
++ buf_uniq.buf);
+ close(fd_listen);
-+ unlink(path);
++ unlink(buf_uniq.buf);
++ strbuf_release(&buf_uniq);
++ errno = saved_errno;
++ return -1;
++ }
+
++ /*
++ * Install it as the "real" socket so that clients will starting
++ * connecting to our socket.
++ */
++ if (rename(buf_uniq.buf, path)) {
++ int saved_errno = errno;
++ error_errno(_("could not create listener socket '%s'"), path);
++ close(fd_listen);
++ unlink(buf_uniq.buf);
++ strbuf_release(&buf_uniq);
+ errno = saved_errno;
-+ return error_errno(_("making listener socket nonblocking '%s'"),
-+ path);
++ return -1;
+ }
+
-+ *inode = st.st_ino;
++ strbuf_release(&buf_uniq);
++ trace2_data_string("ipc-server", NULL, "try-listen", path);
++ return fd_listen;
++}
++
++static int setup_listener_socket(const char *path, struct stat *st_socket,
++ const struct ipc_server_opts *ipc_opts)
++{
++ int fd_listen;
++
++ trace2_region_enter("ipc-server", "create-listener_socket", NULL);
++ fd_listen = create_listener_socket(path, ipc_opts, st_socket);
++ trace2_region_leave("ipc-server", "create-listener_socket", NULL);
+
+ return fd_listen;
+}
@@ compat/simple-ipc/ipc-unix-socket.c (new)
+{
+ struct ipc_server_data *server_data;
+ int fd_listen;
-+ ino_t inode_listen;
++ struct stat st_listen;
+ int sv[2];
+ int k;
+ int nr_threads = opts->nr_threads;
@@ compat/simple-ipc/ipc-unix-socket.c (new)
+ path);
+ }
+
-+ fd_listen = setup_listener_socket(path, &inode_listen, opts);
++ fd_listen = setup_listener_socket(path, &st_listen, opts);
+ if (fd_listen < 0) {
+ int saved_errno = errno;
+ close(sv[0]);
@@ compat/simple-ipc/ipc-unix-socket.c (new)
+ server_data->accept_thread->magic = MAGIC_ACCEPT_THREAD_DATA;
+ server_data->accept_thread->server_data = server_data;
+ server_data->accept_thread->fd_listen = fd_listen;
-+ server_data->accept_thread->inode_listen = inode_listen;
++ server_data->accept_thread->st_listen = st_listen;
+ server_data->accept_thread->fd_send_shutdown = sv[0];
+ server_data->accept_thread->fd_wait_shutdown = sv[1];
+Signature · 1 line
gitgitgadget
PATCH v2 4/14 pkt-line: optionally skip the flush packet in write_packetized_from_buf()
From: Johannes Schindelin <[email protected]>
This function currently has only one caller: apply_multi_file_filter() in convert.c. That caller wants a flush packet to be written after writing the payload.
However, we are about to introduce a user that wants to write many packets before a final flush packet, so let's extend this function to prepare for that scenario.
--- convert.c | 2 +- pkt-line.c | 9 ++++++--- pkt-line.h | 4 +++- 3 files changed, 10 insertions(+), 5 deletions(-)
convert.c +1 -1
@@ -886,7 +886,7 @@ static int apply_multi_file_filter(const char *path, const char *src, size_t lenif (fd >=0 ) err =write_packetized_from_fd (fd, process->in);else - err =write_packetized_from_buf (src, len, process->in); + err =write_packetized_from_buf (src, len, process->in,1 );if (err) goto done;
pkt-line.c +6 -3
@@ -275,14 +275,17 @@ int write_packetized_from_fd(int fd_in, int fd_out)return err; } -int write_packetized_from_buf (const char *src_in,size_t len,int fd_out) +int write_packetized_from_buf (const char *src_in,size_t len,int fd_out, +int flush_at_end) {static struct packet_scratch_space scratch; -return write_packetized_from_buf2 (src_in, len, fd_out, &scratch); +return write_packetized_from_buf2 (src_in, len, fd_out, + flush_at_end, &scratch); }int write_packetized_from_buf2 (const char *src_in,size_t len,int fd_out, +int flush_at_end,struct packet_scratch_space *scratch) {int err =0 ;
@@ -299,7 +302,7 @@ int write_packetized_from_buf2(const char *src_in, size_t len, int fd_out, err =packet_write_gently (fd_out, src_in + bytes_written, bytes_to_write, scratch); bytes_written += bytes_to_write; } -if (!err) +if (!err && flush_at_end) err =packet_flush_gently (fd_out);return err; }
pkt-line.h +3 -1
@@ -40,8 +40,10 @@ void packet_buf_write_len(struct strbuf *buf, const char *data, size_t len);int packet_flush_gently (int fd);int packet_write_fmt_gently (int fd,const char *fmt, ...) __attribute__((format (printf,2 ,3 )));int write_packetized_from_fd (int fd_in,int fd_out); -int write_packetized_from_buf (const char *src_in,size_t len,int fd_out); +int write_packetized_from_buf (const char *src_in,size_t len,int fd_out, +int flush_at_end);int write_packetized_from_buf2 (const char *src_in,size_t len,int fd_out, +int flush_at_end,struct packet_scratch_space *scratch);/*
Signature · 1 line
gitgitgadget
PATCH v2 5/14 pkt-line: (optionally) libify the packet readers
In reply to Jeff Hostetler via GitGitGadgetFrom: Johannes Schindelin <[email protected]>
So far, the (possibly indirect) callers of get_packet_data() can ask that function to return an error instead of die()ing upon end-of-file. However, random read errors will still cause the process to die.
So let's introduce an explicit option to tell the packet reader machinery to please be nice and only return an error.
This change prepares pkt-line for use by long-running daemon processes. Such processes should be able to serve multiple concurrent clients and and survive random IO errors. If there is an error on one connection, a daemon should be able to drop that connection and continue serving existing and future connections.
This ability will be used by a Git-aware "Internal FSMonitor" feature in a later patch series.
--- pkt-line.c | 19 +++++++++++++++++-- pkt-line.h | 4 ++++ 2 files changed, 21 insertions(+), 2 deletions(-)
pkt-line.c +17 -2
@@ -323,8 +323,11 @@ static int get_packet_data(int fd, char **src_buf, size_t *src_size, *src_size -= ret; }else { ret =read_in_full (fd, dst, size); -if (ret <0 ) +if (ret <0 ) { +if (options &PACKET_READ_NEVER_DIE ) +return error_errno (_ ("read error" ));die_errno (_ ("read error" )); + } }/* And complain if we didn't get enough bytes to satisfy the read. */
@@ -332,6 +335,8 @@ static int get_packet_data(int fd, char **src_buf, size_t *src_size,if (options &PACKET_READ_GENTLE_ON_EOF )return -1 ; +if (options &PACKET_READ_NEVER_DIE ) +return error (_ ("the remote end hung up unexpectedly" ));die (_ ("the remote end hung up unexpectedly" )); }
@@ -360,6 +365,9 @@ enum packet_read_status packet_read_with_status(int fd, char **src_buffer, len =packet_length (linelen);if (len <0 ) { +if (options &PACKET_READ_NEVER_DIE ) +return error (_ ("protocol error: bad line length " +"character: %.4s" ), linelen);die (_ ("protocol error: bad line length character: %.4s" ), linelen); }else if (!len) {packet_trace ("0000" ,4 ,0 );
@@ -374,12 +382,19 @@ enum packet_read_status packet_read_with_status(int fd, char **src_buffer, *pktlen =0 ;return PACKET_READ_RESPONSE_END ; }else if (len <4 ) { +if (options &PACKET_READ_NEVER_DIE ) +return error (_ ("protocol error: bad line length %d" ), + len);die (_ ("protocol error: bad line length %d" ), len); } len -=4 ; -if ((unsigned )len >= size) +if ((unsigned )len >= size) { +if (options &PACKET_READ_NEVER_DIE ) +return error (_ ("protocol error: bad line length %d" ), + len);die (_ ("protocol error: bad line length %d" ), len); + }if (get_packet_data (fd, src_buffer, src_len, buffer, len, options) <0 ) { *pktlen =-1 ;
pkt-line.h +4 -0
@@ -79,10 +79,14 @@ int write_packetized_from_buf2(const char *src_in, size_t len, int fd_out,* * If options contains PACKET_READ_DIE_ON_ERR_PACKET, it dies when it sees an * ERR packet. +* +* With `PACKET_READ_NEVER_DIE`, no errors are allowed to trigger die() (except +* an ERR packet, when `PACKET_READ_DIE_ON_ERR_PACKET` is in effect). */ #define PACKET_READ_GENTLE_ON_EOF (1u<<0)#define PACKET_READ_CHOMP_NEWLINE (1u<<1)#define PACKET_READ_DIE_ON_ERR_PACKET (1u<<2) +#define PACKET_READ_NEVER_DIE (1u<<3)int packet_read (int fd,char **src_buffer,size_t *src_len,char *buffer,unsigned size,int options);
Signature · 1 line
gitgitgadget
PATCH v2 6/14 pkt-line: accept additional options in read_packetized_to_strbuf()
In reply to Jeff Hostetler via GitGitGadgetFrom: Johannes Schindelin <[email protected]>
The read_packetized_to_strbuf() function reads packets into a strbuf until a flush packet has been received. So far, it has only one caller:apply_multi_file_filter() in convert.c. This caller really only needs the PACKET_READ_GENTLE_ON_EOF option to be passed to packet_read() (which makes sense in the scenario where packets should be read until a flush packet is received).
We are about to introduce a caller that wants to pass other options through to packet_read(), so let's extend the function signature accordingly.
--- convert.c | 2 +- pkt-line.c | 4 ++-- pkt-line.h | 6 +++++- 3 files changed, 8 insertions(+), 4 deletions(-)
convert.c +1 -1
@@ -903,7 +903,7 @@ static int apply_multi_file_filter(const char *path, const char *src, size_t lenif (err) goto done; - err =read_packetized_to_strbuf (process->out, &nbuf) <0 ; + err =read_packetized_to_strbuf (process->out, &nbuf,0 ) <0 ;if (err) goto done;
pkt-line.c +2 -2
@@ -461,7 +461,7 @@ char *packet_read_line_buf(char **src, size_t *src_len, int *dst_len)return packet_read_line_generic (-1 , src, src_len, dst_len); } -ssize_t read_packetized_to_strbuf (int fd_in,struct strbuf *sb_out) +ssize_t read_packetized_to_strbuf (int fd_in,struct strbuf *sb_out,int options) {int packet_len;
@@ -477,7 +477,7 @@ ssize_t read_packetized_to_strbuf(int fd_in, struct strbuf *sb_out)* that there is already room for the extra byte. */ sb_out->buf + sb_out->len,LARGE_PACKET_DATA_MAX +1 , - PACKET_READ_GENTLE_ON_EOF); + options | PACKET_READ_GENTLE_ON_EOF);if (packet_len <=0 )break ; sb_out->len += packet_len;
pkt-line.h +5 -1
@@ -145,8 +145,12 @@ char *packet_read_line_buf(char **src_buf, size_t *src_len, int *size);/* * Reads a stream of variable sized packets until a flush packet is detected. +* +* The options are augmented by PACKET_READ_GENTLE_ON_EOF and passed to +* packet_read. */ -ssize_t read_packetized_to_strbuf (int fd_in,struct strbuf *sb_out); +ssize_t read_packetized_to_strbuf (int fd_in,struct strbuf *sb_out, +int options);/* * Receive multiplexed output stream over git native protocol.
Signature · 1 line
gitgitgadget
PATCH v2 7/14 simple-ipc: design documentation for new IPC mechanism
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Brief design documentation for new IPC mechanism allowing foreground Git client to talk with an existing daemon process at a known location using a named pipe or unix domain socket.
--- Documentation/technical/api-simple-ipc.txt | 34 ++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 Documentation/technical/api-simple-ipc.txt
Documentation/technical/api-simple-ipc.txt new file +34 -0
@@ -0,0 +1,34 @@ +simple-ipc API +============== + +The simple-ipc API is used to send an IPC message and response between +a (presumably) foreground Git client process to a background server or +daemon process. The server process must already be running. Multiple +client processes can simultaneously communicate with the server +process. + +Communication occurs over a named pipe on Windows and a Unix domain +socket on other platforms. Clients and the server rendezvous at a +previously agreed-to application-specific pathname (which is outside +the scope of this design). + +This IPC mechanism differs from the existing `sub-process.c` model +(Documentation/technical/long-running-process-protocol.txt) and used +by applications like Git-LFS. In the simple-ipc model the server is +assumed to be a very long-running system service. In contrast, in the +LFS-style sub-process model the helper is started with the foreground +process and exits when the foreground process terminates. + +How the simple-ipc server is started is also outside the scope of the +IPC mechanism. For example, the server might be started during +maintenance operations. + +The IPC protocol consists of a single request message from the client and +an optional request message from the server. For simplicity, pkt-line +routines are used to hide chunking and buffering concerns. Each side +terminates their message with a flush packet. +(Documentation/technical/protocol-common.txt) + +The actual format of the client and server messages is application +specific. The IPC layer transmits and receives an opaque buffer without +any concern for the content within.
Signature · 1 line
gitgitgadget
PATCH v2 11/14 unix-socket: add options to unix_stream_listen()
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Update unix_stream_listen() to take an options structure to override default behaviors. This includes the size of the listen() backlog and whether it should always unlink the socket file before trying to create a new one. Also eliminate calls to die() if it cannot create a socket.
Normally, unix_stream_listen() always tries to unlink() the socket-path before calling bind(). If there is an existing server/daemon already bound and listening on that socket-path, our unlink() would have the effect of disassociating the existing server's bound-socket-fd from the socket-path without notifying the existing server. The existing server could continue to service existing connections (accepted-socket-fd's), but would not receive any futher new connections (since clients rendezvous via the socket-path). The existing server would effectively be offline but yet appear to be active.
Furthermore, unix_stream_listen() creates an opportunity for a brief race condition for connecting clients if they try to connect in the interval between the forced unlink() and the subsequent bind() (which recreates the socket-path that is bound to a new socket-fd in the current process).
--- builtin/credential-cache--daemon.c | 3 ++- unix-socket.c | 28 +++++++++++++++++++++------- unix-socket.h | 14 +++++++++++++- 3 files changed, 36 insertions(+), 9 deletions(-)
builtin/credential-cache--daemon.c +2 -1
@@ -203,9 +203,10 @@ static int serve_cache_loop(int fd)static void serve_cache (const char *socket_path,int debug) { +struct unix_stream_listen_opts opts =UNIX_STREAM_LISTEN_OPTS_INIT ;int fd; - fd =unix_stream_listen (socket_path); + fd =unix_stream_listen (socket_path, &opts);if (fd <0 )die_errno ("unable to bind to '%s'" , socket_path);
unix-socket.c +21 -7
@@ -88,24 +88,35 @@ int unix_stream_connect(const char *path)return -1 ; } -int unix_stream_listen (const char *path) +int unix_stream_listen (const char *path, +const struct unix_stream_listen_opts *opts) { -int fd, saved_errno; +int fd =-1 ; +int saved_errno; +int bind_successful =0 ; +int backlog;struct sockaddr_un sa;struct unix_sockaddr_context ctx; -unlink (path); -if (unix_sockaddr_init (&sa, path, &ctx) <0 )return -1 ; + fd =socket (AF_UNIX ,SOCK_STREAM ,0 );if (fd <0 ) -die_errno ("unable to create socket" ); + goto fail; + +if (opts->force_unlink_before_bind) +unlink (path);if (bind (fd, (struct sockaddr *)&sa,sizeof (sa)) <0 ) goto fail; + bind_successful =1 ; -if (listen (fd,5 ) <0 ) +if (opts->listen_backlog_size >0 ) + backlog = opts->listen_backlog_size; +else + backlog =5 ; +if (listen (fd, backlog) <0 ) goto fail;unix_sockaddr_cleanup (&ctx);
@@ -114,7 +125,10 @@ int unix_stream_listen(const char *path) fail: saved_errno = errno;unix_sockaddr_cleanup (&ctx); -close (fd); +if (fd !=-1 ) +close (fd); +if (bind_successful) +unlink (path); errno = saved_errno;return -1 ; }
unix-socket.h +13 -1
@@ -1,7 +1,19 @@#ifndef UNIX_SOCKET_H #define UNIX_SOCKET_H +struct unix_stream_listen_opts { +int listen_backlog_size; +unsigned int force_unlink_before_bind:1 ; +}; + +#define UNIX_STREAM_LISTEN_OPTS_INIT \ +{ \ + .listen_backlog_size = 5, \ + .force_unlink_before_bind = 1, \ +} +int unix_stream_connect (const char *path); -int unix_stream_listen (const char *path); +int unix_stream_listen (const char *path, +const struct unix_stream_listen_opts *opts);#endif /* UNIX_SOCKET_H */
Signature · 1 line
gitgitgadget
PATCH v2 13/14 unix-socket: do not call die in unix_stream_connect()
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Teach unix_stream_connect() to return error rather than calling die() when a socket cannot be created.
--- unix-socket.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-)
unix-socket.c +6 -3
@@ -76,15 +76,17 @@ static int unix_sockaddr_init(struct sockaddr_un *sa, const char *path,int unix_stream_connect (const char *path) { -int fd, saved_errno; +int fd =-1 ; +int saved_errno;struct sockaddr_un sa;struct unix_sockaddr_context ctx =UNIX_SOCKADDR_CONTEXT_INIT ;if (unix_sockaddr_init (&sa, path, &ctx) <0 )return -1 ; + fd =socket (AF_UNIX ,SOCK_STREAM ,0 );if (fd <0 ) -die_errno ("unable to create socket" ); + goto fail;if (connect (fd, (struct sockaddr *)&sa,sizeof (sa)) <0 ) goto fail;
@@ -94,7 +96,8 @@ int unix_stream_connect(const char *path) fail: saved_errno = errno;unix_sockaddr_cleanup (&ctx); -close (fd); +if (fd !=-1 ) +close (fd); errno = saved_errno;return -1 ; }
Signature · 1 line
gitgitgadget
PATCH v2 14/14 simple-ipc: add Unix domain socket implementation
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Create Unix domain socket based implementation of "simple-ipc".
A set of ipc_client routines implement a client library to connect to an ipc_server over a Unix domain socket, send a simple request, and receive a single response. Clients use blocking IO on the socket.
A set of ipc_server routines implement a thread pool to listen for and concurrently service client connections.
The server creates a new Unix domain socket at a known location. If a socket already exists with that name, the server tries to determine if another server is already listening on the socket or if the socket is dead. If socket is busy, the server exits with an error rather than stealing the socket. If the socket is dead, the server creates a new one and starts up.
If while running, the server detects that its socket has been stolen by another server, it automatically exits.
--- Makefile | 2 + compat/simple-ipc/ipc-unix-socket.c | 1127 +++++++++++++++++++++++++++ contrib/buildsystems/CMakeLists.txt | 2 + simple-ipc.h | 7 +- 4 files changed, 1137 insertions(+), 1 deletion(-) create mode 100644 compat/simple-ipc/ipc-unix-socket.c
Makefile +2 -0
@@ -1681,6 +1681,8 @@ ifdef NO_UNIX_SOCKETS BASIC_CFLAGS += -DNO_UNIX_SOCKETS else LIB_OBJS += unix-socket.o + LIB_OBJS += compat/simple-ipc/ipc-shared.o + LIB_OBJS += compat/simple-ipc/ipc-unix-socket.o endif ifdef USE_WIN32_IPC
compat/simple-ipc/ipc-unix-socket.c new file +1127 -0
1,127 lines not on this page · raw message
contrib/buildsystems/CMakeLists.txt +2 -0
@@ -248,6 +248,8 @@ endif() if(CMAKE_SYSTEM_NAME STREQUAL "Windows") list(APPEND compat_SOURCES compat/simple-ipc/ipc-shared.c compat/simple-ipc/ipc-win32.c) +else() + list(APPEND compat_SOURCES compat/simple-ipc/ipc-shared.c compat/simple-ipc/ipc-unix-socket.c) endif() set(EXE_EXTENSION ${CMAKE_EXECUTABLE_SUFFIX})
simple-ipc.h +6 -1
@@ -5,7 +5,7 @@* See Documentation/technical/api-simple-ipc.txt */ -#if defined(GIT_WINDOWS_NATIVE ) +#if defined(GIT_WINDOWS_NATIVE ) || !defined(NO_UNIX_SOCKETS )#define SUPPORTS_SIMPLE_IPC #endif
@@ -160,6 +160,11 @@ struct ipc_server_data;struct ipc_server_opts {int nr_threads; + +/* +* Disallow chdir() when creating a Unix domain socket. +*/ +unsigned int uds_disallow_chdir:1 ; };/*
Signature · 1 line
gitgitgadget
PATCH v2 8/14 simple-ipc: add win32 implementation
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Create Windows implementation of "simple-ipc" using named pipes.
--- Makefile | 5 + compat/simple-ipc/ipc-shared.c | 28 ++ compat/simple-ipc/ipc-win32.c | 751 ++++++++++++++++++++++++++++ config.mak.uname | 2 + contrib/buildsystems/CMakeLists.txt | 4 + simple-ipc.h | 225 +++++++++ 6 files changed, 1015 insertions(+) create mode 100644 compat/simple-ipc/ipc-shared.c create mode 100644 compat/simple-ipc/ipc-win32.c create mode 100644 simple-ipc.h
Makefile +5 -0
@@ -1682,6 +1682,11 @@ else LIB_OBJS += unix-socket.o endif +ifdef USE_WIN32_IPC + LIB_OBJS += compat/simple-ipc/ipc-shared.o + LIB_OBJS += compat/simple-ipc/ipc-win32.o +endif + ifdef NO_ICONV BASIC_CFLAGS += -DNO_ICONV endif
compat/simple-ipc/ipc-shared.c new file +28 -0
@@ -0,0 +1,28 @@ +#include "cache.h" +#include "simple-ipc.h" +#include "strbuf.h" +#include "pkt-line.h" +#include "thread-utils.h" + +#ifdef SUPPORTS_SIMPLE_IPC + +int ipc_server_run (const char *path,const struct ipc_server_opts *opts, +ipc_server_application_cb *application_cb, +void *application_data) +{ +struct ipc_server_data *server_data =NULL ; +int ret; + + ret =ipc_server_run_async (&server_data, path, opts, + application_cb, application_data); +if (ret) +return ret; + + ret =ipc_server_await (server_data); + +ipc_server_free (server_data); + +return ret; +} + +#endif /* SUPPORTS_SIMPLE_IPC */
compat/simple-ipc/ipc-win32.c new file +751 -0
751 lines not on this page · raw message
config.mak.uname +2 -0
@@ -421,6 +421,7 @@ ifeq ($(uname_S),Windows) RUNTIME_PREFIX = YesPlease HAVE_WPGMPTR = YesWeDo NO_ST_BLOCKS_IN_STRUCT_STAT = YesPlease + USE_WIN32_IPC = YesPlease USE_WIN32_MMAP = YesPlease MMAP_PREVENTS_DELETE = UnfortunatelyYes # USE_NED_ALLOCATOR = YesPlease
@@ -597,6 +598,7 @@ ifneq (,$(findstring MINGW,$(uname_S))) RUNTIME_PREFIX = YesPlease HAVE_WPGMPTR = YesWeDo NO_ST_BLOCKS_IN_STRUCT_STAT = YesPlease + USE_WIN32_IPC = YesPlease USE_WIN32_MMAP = YesPlease MMAP_PREVENTS_DELETE = UnfortunatelyYes USE_NED_ALLOCATOR = YesPlease
contrib/buildsystems/CMakeLists.txt +4 -0
@@ -246,6 +246,10 @@ elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux") list(APPEND compat_SOURCES unix-socket.c) endif() +if(CMAKE_SYSTEM_NAME STREQUAL "Windows") + list(APPEND compat_SOURCES compat/simple-ipc/ipc-shared.c compat/simple-ipc/ipc-win32.c) +endif() + set(EXE_EXTENSION ${CMAKE_EXECUTABLE_SUFFIX}) #header checks
simple-ipc.h new file +225 -0
225 lines not on this page · raw message
Signature · 1 line
gitgitgadget
PATCH v2 10/14 unix-socket: elimiate static unix_stream_socket() helper function
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
The static helper function unix_stream_socket() calls die(). This is not appropriate for all callers. Eliminate the wrapper function and move the existing error handling to the callers in preparation for adapting specific callers.
--- unix-socket.c | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-)
unix-socket.c +7 -10
@@ -1,14 +1,6 @@#include "cache.h" #include "unix-socket.h" -static int unix_stream_socket (void ) -{ -int fd =socket (AF_UNIX ,SOCK_STREAM ,0 ); -if (fd <0 ) -die_errno ("unable to create socket" ); -return fd; -} -static int chdir_len (const char *orig,int len) {char *path =xmemdupz (orig, len);
@@ -79,7 +71,10 @@ int unix_stream_connect(const char *path)if (unix_sockaddr_init (&sa, path, &ctx) <0 )return -1 ; - fd =unix_stream_socket (); + fd =socket (AF_UNIX ,SOCK_STREAM ,0 ); +if (fd <0 ) +die_errno ("unable to create socket" ); +if (connect (fd, (struct sockaddr *)&sa,sizeof (sa)) <0 ) goto fail;unix_sockaddr_cleanup (&ctx);
@@ -103,7 +98,9 @@ int unix_stream_listen(const char *path)if (unix_sockaddr_init (&sa, path, &ctx) <0 )return -1 ; - fd =unix_stream_socket (); + fd =socket (AF_UNIX ,SOCK_STREAM ,0 ); +if (fd <0 ) +die_errno ("unable to create socket" );if (bind (fd, (struct sockaddr *)&sa,sizeof (sa)) <0 ) goto fail;
Signature · 1 line
gitgitgadget
PATCH v2 9/14 simple-ipc: add t/helper/test-simple-ipc and t0052
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Create unit tests for "simple-ipc". These are currently only enabled on Windows.
--- Makefile | 1 + t/helper/test-simple-ipc.c | 485 +++++++++++++++++++++++++++++++++++++ t/helper/test-tool.c | 1 + t/helper/test-tool.h | 1 + t/t0052-simple-ipc.sh | 129 ++++++++++ 5 files changed, 617 insertions(+) create mode 100644 t/helper/test-simple-ipc.c create mode 100755 t/t0052-simple-ipc.sh
Makefile +1 -0
@@ -740,6 +740,7 @@ TEST_BUILTINS_OBJS += test-serve-v2.o TEST_BUILTINS_OBJS += test-sha1.o TEST_BUILTINS_OBJS += test-sha256.o TEST_BUILTINS_OBJS += test-sigchain.o +TEST_BUILTINS_OBJS += test-simple-ipc.o TEST_BUILTINS_OBJS += test-strcmp-offset.o TEST_BUILTINS_OBJS += test-string-list.o TEST_BUILTINS_OBJS += test-submodule-config.o
t/helper/test-simple-ipc.c new file +485 -0
485 lines not on this page · raw message
t/helper/test-tool.c +1 -0
@@ -64,6 +64,7 @@ static struct test_cmd cmds[] = { { "sha1", cmd__sha1 }, { "sha256", cmd__sha256 }, { "sigchain", cmd__sigchain }, + { "simple-ipc", cmd__simple_ipc }, { "strcmp-offset", cmd__strcmp_offset }, { "string-list", cmd__string_list }, { "submodule-config", cmd__submodule_config },
t/helper/test-tool.h +1 -0
@@ -54,6 +54,7 @@ int cmd__sha1(int argc, const char **argv);int cmd__oid_array (int argc,const char **argv);int cmd__sha256 (int argc,const char **argv);int cmd__sigchain (int argc,const char **argv); +int cmd__simple_ipc (int argc,const char **argv);int cmd__strcmp_offset (int argc,const char **argv);int cmd__string_list (int argc,const char **argv);int cmd__submodule_config (int argc,const char **argv);
t/t0052-simple-ipc.sh new file +129 -0
129 lines not on this page · raw message
Signature · 1 line
gitgitgadget
PATCH v2 12/14 unix-socket: add no-chdir option to unix_stream_listen()
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Calls to chdir() are dangerous in a multi-threaded context. If unix_stream_listen() is given a socket pathname that is too big to fit in a sockaddr_un structure, it will chdir() to the parent directory of the requested socket pathname, create the socket using a relative pathname, and then chdir() back. This is not thread-safe.
Add disallow_chdir flag to struct unix_sockaddr_context and change all callers to pass an initialized context structure.
Teach unix_sockaddr_init() to not allow calls to chdir() when flag is set.
--- unix-socket.c | 19 ++++++++++++++++--- unix-socket.h | 2 ++ 2 files changed, 18 insertions(+), 3 deletions(-)
unix-socket.c +16 -3
@@ -11,8 +11,15 @@ static int chdir_len(const char *orig, int len)struct unix_sockaddr_context {char *orig_dir; +unsigned int disallow_chdir:1 ; }; +#define UNIX_SOCKADDR_CONTEXT_INIT \ +{ \ + .orig_dir=NULL, \ + .disallow_chdir=0, \ +} +static void unix_sockaddr_cleanup (struct unix_sockaddr_context *ctx) {if (!ctx->orig_dir)
@@ -32,7 +39,11 @@ static int unix_sockaddr_init(struct sockaddr_un *sa, const char *path, {int size =strlen (path) +1 ; - ctx->orig_dir =NULL ; +if (ctx->disallow_chdir && size >sizeof (sa->sun_path)) { + errno =ENAMETOOLONG ; +return -1 ; + } +if (size >sizeof (sa->sun_path)) {const char *slash =find_last_dir_sep (path);const char *dir;
@@ -67,7 +78,7 @@ int unix_stream_connect(const char *path) {int fd, saved_errno;struct sockaddr_un sa; -struct unix_sockaddr_context ctx; +struct unix_sockaddr_context ctx =UNIX_SOCKADDR_CONTEXT_INIT ;if (unix_sockaddr_init (&sa, path, &ctx) <0 )return -1 ;
@@ -96,7 +107,9 @@ int unix_stream_listen(const char *path,int bind_successful =0 ;int backlog;struct sockaddr_un sa; -struct unix_sockaddr_context ctx; +struct unix_sockaddr_context ctx =UNIX_SOCKADDR_CONTEXT_INIT ; + + ctx.disallow_chdir = opts->disallow_chdir;if (unix_sockaddr_init (&sa, path, &ctx) <0 )return -1 ;
unix-socket.h +2 -0
@@ -4,12 +4,14 @@struct unix_stream_listen_opts {int listen_backlog_size;unsigned int force_unlink_before_bind:1 ; +unsigned int disallow_chdir:1 ; };#define UNIX_STREAM_LISTEN_OPTS_INIT \ { \ .listen_backlog_size = 5, \ .force_unlink_before_bind = 1, \ + .disallow_chdir = 0, \ }int unix_stream_connect (const char *path);
Signature · 1 line
gitgitgadget
PATCH v2 3/14 pkt-line: add write_packetized_from_buf2() that takes scratch buffer
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Create version of write_packetized_from_buf() that takes a scratch buffer argument rather than assuming a static buffer. This will be used later as we make packet-line writing more thread-safe.
--- pkt-line.c | 9 ++++++++- pkt-line.h | 2 ++ 2 files changed, 10 insertions(+), 1 deletion(-)
pkt-line.c +8 -1
@@ -278,6 +278,13 @@ int write_packetized_from_fd(int fd_in, int fd_out)int write_packetized_from_buf (const char *src_in,size_t len,int fd_out) {static struct packet_scratch_space scratch; + +return write_packetized_from_buf2 (src_in, len, fd_out, &scratch); +} + +int write_packetized_from_buf2 (const char *src_in,size_t len,int fd_out, +struct packet_scratch_space *scratch) +{int err =0 ;size_t bytes_written =0 ;size_t bytes_to_write;
@@ -289,7 +296,7 @@ int write_packetized_from_buf(const char *src_in, size_t len, int fd_out) bytes_to_write = len - bytes_written;if (bytes_to_write ==0 )break ; - err =packet_write_gently (fd_out, src_in + bytes_written, bytes_to_write, &scratch); + err =packet_write_gently (fd_out, src_in + bytes_written, bytes_to_write, scratch); bytes_written += bytes_to_write; }if (!err)
pkt-line.h +2 -0
@@ -41,6 +41,8 @@ int packet_flush_gently(int fd);int packet_write_fmt_gently (int fd,const char *fmt, ...) __attribute__((format (printf,2 ,3 )));int write_packetized_from_fd (int fd_in,int fd_out);int write_packetized_from_buf (const char *src_in,size_t len,int fd_out); +int write_packetized_from_buf2 (const char *src_in,size_t len,int fd_out, +struct packet_scratch_space *scratch);/* * Read a packetized line into the buffer, which must be at least size bytes
Signature · 1 line
gitgitgadget
PATCH v2 2/14 pkt-line: promote static buffer in packet_write_gently() to callers
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Move the static buffer used in packet_write_gently() to its callers. This is a first step to make packet writing more thread-safe.
--- pkt-line.c | 33 ++++++++++++++++++++++++--------- pkt-line.h | 10 ++++++++-- 2 files changed, 32 insertions(+), 11 deletions(-)
pkt-line.c +24 -9
@@ -194,26 +194,34 @@ int packet_write_fmt_gently(int fd, const char *fmt, ...)return status; } -static int packet_write_gently (const int fd_out,const char *buf,size_t size) +/* +* Use the provided scratch space to build a combined <hdr><buf> buffer +* and write it to the file descriptor (in one write if possible). +*/ +static int packet_write_gently (const int fd_out,const char *buf,size_t size, +struct packet_scratch_space *scratch) { -static char packet_write_buffer[LARGE_PACKET_MAX ];size_t packet_size; -if (size >sizeof (packet_write_buffer) -4 ) +if (size >sizeof (scratch->buffer) -4 )return error (_ ("packet write failed - data exceeds max packet size" ));packet_trace (buf, size,1 ); packet_size = size +4 ; -set_packet_header (packet_write_buffer, packet_size); -memcpy (packet_write_buffer +4 , buf, size); -if (write_in_full (fd_out, packet_write_buffer, packet_size) <0 ) + +set_packet_header (scratch->buffer, packet_size); +memcpy (scratch->buffer +4 , buf, size); + +if (write_in_full (fd_out, scratch->buffer, packet_size) <0 )return error (_ ("packet write failed" ));return 0 ; }void packet_write (int fd_out,const char *buf,size_t size) { -if (packet_write_gently (fd_out, buf, size)) +static struct packet_scratch_space scratch; + +if (packet_write_gently (fd_out, buf, size, &scratch))die_errno (_ ("packet write failed" )); }
@@ -244,6 +252,12 @@ void packet_buf_write_len(struct strbuf *buf, const char *data, size_t len)int write_packetized_from_fd (int fd_in,int fd_out) { +/* +* TODO We could save a memcpy() if we essentially inline +* TODO packet_write_gently() here and change the xread() +* TODO to pass &buf[4]. +*/ +static struct packet_scratch_space scratch;static char buf[LARGE_PACKET_DATA_MAX ];int err =0 ;ssize_t bytes_to_write;
@@ -254,7 +268,7 @@ int write_packetized_from_fd(int fd_in, int fd_out)return COPY_READ_ERROR ;if (bytes_to_write ==0 )break ; - err =packet_write_gently (fd_out, buf, bytes_to_write); + err =packet_write_gently (fd_out, buf, bytes_to_write, &scratch); }if (!err) err =packet_flush_gently (fd_out);
@@ -263,6 +277,7 @@ int write_packetized_from_fd(int fd_in, int fd_out)int write_packetized_from_buf (const char *src_in,size_t len,int fd_out) { +static struct packet_scratch_space scratch;int err =0 ;size_t bytes_written =0 ;size_t bytes_to_write;
@@ -274,7 +289,7 @@ int write_packetized_from_buf(const char *src_in, size_t len, int fd_out) bytes_to_write = len - bytes_written;if (bytes_to_write ==0 )break ; - err =packet_write_gently (fd_out, src_in + bytes_written, bytes_to_write); + err =packet_write_gently (fd_out, src_in + bytes_written, bytes_to_write, &scratch); bytes_written += bytes_to_write; }if (!err)
pkt-line.h +8 -2
@@ -5,6 +5,13 @@#include "strbuf.h" #include "sideband.h" +#define LARGE_PACKET_MAX 65520 +#define LARGE_PACKET_DATA_MAX (LARGE_PACKET_MAX - 4) + +struct packet_scratch_space { +char buffer[LARGE_PACKET_MAX ]; +}; +/* * Write a packetized stream, where each line is preceded by * its length (including the header) as a 4-byte hex number.
@@ -213,8 +220,7 @@ enum packet_read_status packet_reader_read(struct packet_reader *reader);enum packet_read_status packet_reader_peek (struct packet_reader *reader);#define DEFAULT_PACKET_MAX 1000 -#define LARGE_PACKET_MAX 65520 -#define LARGE_PACKET_DATA_MAX (LARGE_PACKET_MAX - 4) +extern char packet_buffer[LARGE_PACKET_MAX ];struct packet_writer {
Signature · 1 line
gitgitgadget
Quoted 5 lines · Jeff Hostetler via GitGitGadget
Here is version 2 of my "Simple IPC" series and addresses the following review comments:
...
Junio C Hamano (1):
ci/install-depends: attempt to fix "brew cask" stuff
Huh?
Quoted 10 lines · Junio C Hamano
Quoted 5 lines · Jeff Hostetler via GitGitGadget
Here is version 2 of my "Simple IPC" series and addresses the following review comments:
...
Junio C Hamano (1):
ci/install-depends: attempt to fix "brew cask" stuffHuh?
Sorry. I had to prepend that one to the patch series to get the CI builds to run. I've been working rebased against "v2.30.0" and GitGitGadget references "master".
Jeff
Re: [PATCH v2 02/14] pkt-line: promote static buffer in packet_write_gently() to callers
In reply to Jeff Hostetler via GitGitGadgetpkt-line.c int packet_write_fmt_gently(int fd, const char *fmt, ...) · line 202
Quoted 7 lines · Jeff Hostetler via GitGitGadget
pkt-line.c +6
-1-static int packet_write_gently (const int fd_out,const char *buf,size_t size) +/* +* Use the provided scratch space to build a combined <hdr><buf> buffer +* and write it to the file descriptor (in one write if possible). +*/ +static int packet_write_gently (const int fd_out,const char *buf,size_t size, +struct packet_scratch_space *scratch)
Thanks for addressing my stack space concern.
This solution does work (and I like wrapping it in a struct like this), though I have to wonder if we're not just punting on the thread issues in an ever-so-slight way with things like this:
pkt-line.c int packet_write_fmt_gently(int fd, const char *fmt, ...) · line 226
Quoted 8 lines
void
packet_write(int fd_out, const char *buf, size_t size){pkt-line.c +3
-1-if (packet_write_gently (fd_out, buf, size)) +static struct packet_scratch_space scratch; + +if (packet_write_gently (fd_out, buf, size, &scratch))die_errno (_ ("packet write failed" )); }
Where we just moved it one step up the call stack.
pkt-line.c void packet_buf_write_len(struct strbuf *buf, const char *data, size_t len) · line 259
Quoted 7 lines
int
write_packetized_from_fd(int fd_in, int fd_out){pkt-line.c +5
-0+/* +* TODO We could save a memcpy() if we essentially inline +* TODO packet_write_gently() here and change the xread() +* TODO to pass &buf[4]. +*/
And comments like this make me wonder if the current crop of pktline functions are just mis-designed in the first place. There are two obvious directions here.
One, we can observe that the only reason we need the scratch space is to ship out the whole thing in a single write():
pkt-line.c int packet_write_fmt_gently(int fd, const char *fmt, ...) · line 216
Quoted 10 lines
[in
packet_write_gently]pkt-line.c +5
-3-set_packet_header (packet_write_buffer, packet_size); -memcpy (packet_write_buffer +4 , buf, size); -if (write_in_full (fd_out, packet_write_buffer, packet_size) <0 ) + +set_packet_header (scratch->buffer, packet_size); +memcpy (scratch->buffer +4 , buf, size); + +if (write_in_full (fd_out, scratch->buffer, packet_size) <0 )return error (_ ("packet write failed" ));
Would it really be so bad to do:
char header[4];
set_packet_header(header, packet_size);
if (write_in_full(fd_out, header, 4) < 0 ||
write_in_full(fd_out, buf, size) < 0)
return error(...);I doubt that two syscalls is breaking the bank here, but if people are really concerned, using writev() would be a much better solution. Obviously we can't rely on it being available everywhere, but it's quite easy to emulate with a wrapper (and I'd be happy punt on any writev stuff until somebody actually measures a difference).
The other direction is that callers could be using a correctly-sized buffer in the first place. I.e., something like:
struct packet_buffer {
char full_packet[LARGE_PACKET_MAX];
};
static inline char *packet_data(struct packet_buffer *pb)
{
return pb->full_packet + 4;}
That lets people work with the oversized buffer in a natural-ish way that would be hard to get wrong, like:
memcpy(packet_data(pb), some_other_buf, len);
(though if we wanted to go even further, we could provide accessors that actually do the writing and sanity-check the lengths; the downside is that I'm not sure how callers typically get the bytes into these bufs in the first place).
That's a much bigger change, of course, and I'd guess you'd much prefer to focus on the actual point of your series. ;)
-Peff
Re: [PATCH v2 03/14] pkt-line: add write_packetized_from_buf2() that takes scratch buffer
In reply to Jeff Hostetler via GitGitGadgetQuoted 5 lines · Jeff Hostetler via GitGitGadget
From: Jeff Hostetler <[email protected]>
Create version of
write_packetized_from_buf()that takes a scratch buffer argument rather than assuming a static buffer. This will be used later as we make packet-line writing more thread-safe.
OK, this is extending the changes from the first patch...
pkt-line.c int write_packetized_from_fd(int fd_in, int fd_out) · line 286
Quoted 9 lines
int
write_packetized_from_buf(const char *src_in, size_t len, int fd_out){pkt-line.c +6
-0static struct packet_scratch_space scratch; + +return write_packetized_from_buf2 (src_in, len, fd_out, &scratch); +} + +int write_packetized_from_buf2 (const char *src_in,size_t len,int fd_out, +struct packet_scratch_space *scratch)
Oof, that name. I know we are guilty of a lot of "foo_1()" helpers for foo(), but they are usually internal static functions that don't get spread around. This one is a public function.
Something like "_with_scratch" might be a bit more descriptive. Though given that there is exactly one caller of the original currently, I'd be tempted to say that it should just learn the scratch-space argument.
(All of this is moot, of course, if you follow either of my suggestions from the earlier patch to drop the need for this scratch space entirely).
-Peff
Re: [PATCH v2 04/14] pkt-line: optionally skip the flush packet in write_packetized_from_buf()
In reply to Johannes Schindelin via GitGitGadgetQuoted 9 lines · Johannes Schindelin via GitGitGadget
From: Johannes Schindelin <[email protected]>
This function currently has only one caller:
apply_multi_file_filter()inconvert.c. That caller wants a flush packet to be written after writing the payload.However, we are about to introduce a user that wants to write many packets before a final flush packet, so let's extend this function to prepare for that scenario.
I think this is a sign that the function is not very well-designed in the first place. It seems like the code would be easier to understand overall if that caller just explicitly did the flush itself. It even already does so in other cases!
Something like (untested):
convert.c | 4 ++++ pkt-line.c | 4 ---- 2 files changed, 4 insertions(+), 4 deletions(-)
convert.c +4 -0
@@ -890,6 +890,10 @@ static int apply_multi_file_filter(const char *path, const char *src, size_t lenif (err) goto done; + err =packet_flush_gently (process->in); +if (err) + goto done; + err =subprocess_read_status (process->out, &filter_status);if (err) goto done;
pkt-line.c +0 -4
@@ -256,8 +256,6 @@ int write_packetized_from_fd(int fd_in, int fd_out)break ; err =packet_write_gently (fd_out, buf, bytes_to_write); } -if (!err) - err =packet_flush_gently (fd_out);return err; }
@@ -277,8 +275,6 @@ int write_packetized_from_buf(const char *src_in, size_t len, int fd_out) err =packet_write_gently (fd_out, src_in + bytes_written, bytes_to_write); bytes_written += bytes_to_write; } -if (!err) - err =packet_flush_gently (fd_out);return err; }
-Peff
Re: [PATCH v2 10/14] unix-socket: elimiate static unix_stream_socket() helper function
In reply to Jeff Hostetler via GitGitGadgetQuoted 6 lines · Jeff Hostetler via GitGitGadget
From: Jeff Hostetler <[email protected]>
The static helper function
unix_stream_socket()callsdie(). This is not appropriate for all callers. Eliminate the wrapper function and move the existing error handling to the callers in preparation for adapting specific callers.
Thanks, this looks good.
unix-socket.c line 10
Quoted 7 lines
unix-socket.c +0
-7-static int unix_stream_socket (void ) -{ -int fd =socket (AF_UNIX ,SOCK_STREAM ,0 ); -if (fd <0 ) -die_errno ("unable to create socket" ); -return fd; -}
This could become a one-liner:
return socket(AF_UNIX, SOCK_STREAM, 0);
to keep the details abstracted. But it's local to this file, the callers are already necessarily full of bsd-socket arcana, and it's not like the magic words there have ever changed in 30+ years. Putting it inline seems quite reasonable. :)
-Peff
Re: [PATCH v2 10/14] unix-socket: elimiate static unix_stream_socket() helper function
In reply to Jeff Hostetler via GitGitGadgetunix-socket.c int unix_stream_connect(const char *path) · line 77
Quoted 12 lines · Jeff Hostetler via GitGitGadget
static int
chdir_len(const char *orig, int len)
{unix-socket.c +4
-1char *path =xmemdupz (orig, len);@@ -79,7 +71,10 @@ int unix_stream_connect(const char *path)if (unix_sockaddr_init (&sa, path, &ctx) <0 )return -1 ; - fd =unix_stream_socket (); + fd =socket (AF_UNIX ,SOCK_STREAM ,0 ); +if (fd <0 ) +die_errno ("unable to create socket" ); +
Reading the next patch, I suddenly realized that these are die calls, and not just passing along the error (which you then fix in the next patch). It seems like that should be happening here in this patch. Callers must already be ready to handle an error (we return -1 in the context above).
unix-socket.c int unix_stream_listen(const char *path) · line 103
Quoted 8 lines
unix-socket.c +3
-1@@ -103,7 +98,9 @@ int unix_stream_listen(const char *path)if (unix_sockaddr_init (&sa, path, &ctx) <0 )return -1 ; - fd =unix_stream_socket (); + fd =socket (AF_UNIX ,SOCK_STREAM ,0 ); +if (fd <0 ) +die_errno ("unable to create socket" );
Ditto here.
-Peff
Re: [PATCH v2 11/14] unix-socket: add options to unix_stream_listen()
In reply to Jeff Hostetler via GitGitGadgetQuoted 7 lines · Jeff Hostetler via GitGitGadget
From: Jeff Hostetler <[email protected]>
Update
unix_stream_listen()to take an options structure to override default behaviors. This includes the size of thelisten()backlog and whether it should always unlink the socket file before trying to create a new one. Also eliminate calls todie()if it cannot create a socket.
I sent a follow-up on the previous patch, but I think this part about the die() should be folded in there.
Likewise I think it would probably be easier to follow if we added the backlog parameter and the unlink options in separate patches. The backlog thing is small, but the unlink part is subtle and requires explanation. That's a good sign it might do better in its own commit.
Quoted 11 lines
Normally,
unix_stream_listen()always tries tounlink()the socket-path before callingbind(). If there is an existing server/daemon already bound and listening on that socket-path, ourunlink()would have the effect of disassociating the existing server's bound-socket-fd from the socket-path without notifying the existing server. The existing server could continue to service existing connections (accepted-socket-fd's), but would not receive any futher new connections (since clients rendezvous via the socket-path). The existing server would effectively be offline but yet appear to be active.
Quoted 5 lines
Furthermore,
unix_stream_listen()creates an opportunity for a brief race condition for connecting clients if they try to connect in the interval between the forcedunlink()and the subsequentbind()(which recreates the socket-path that is bound to a new socket-fd in the current process).
OK. I'm still not sure of the endgame here for writing non-racy code to establish the socket (which is going to require either some atomic renaming or some dot-locking in the caller). But it's plausible to me that this option will be a useful primitive.
The implementation looks correct, though here are a few small observations/questions/nits:
Quoted 6 lines
unix-socket.c +3
-2-int unix_stream_listen (const char *path) +int unix_stream_listen (const char *path, +const struct unix_stream_listen_opts *opts) { -int fd, saved_errno; +int fd =-1 ;
unix-socket.c int unix_stream_connect(const char *path) · line 102
Quoted 10 lines
unix-socket.c +3
-2+int saved_errno; +int bind_successful =0 ; +int backlog;struct sockaddr_un sa;struct unix_sockaddr_context ctx; -unlink (path); -if (unix_sockaddr_init (&sa, path, &ctx) <0 )return -1 ;
We can return directly here, because we know there is nothing to clean up. Which I thought mean that here...
unix-socket.c int unix_stream_connect(const char *path) · line 106
Quoted 5 lines
unix-socket.c +2
-1+ fd =socket (AF_UNIX ,SOCK_STREAM ,0 );if (fd <0 ) -die_errno ("unable to create socket" ); + goto fail;
...we are in the same boat. We did not create a socket, so we can just return. That makes our cleanup code a bit simpler. But we can't do that, because unix_sockaddr_init() may have done things that need cleaning up (like chdir). So what you have here is correct.
IMHO that is all the more reason to push this (and the similar code in unix_stream_connect() added in patch 13) into the previous patch.
unix-socket.c int unix_stream_connect(const char *path) · line 113
Quoted 6 lines
unix-socket.c +3
-0+if (opts->force_unlink_before_bind) +unlink (path);if (bind (fd, (struct sockaddr *)&sa,sizeof (sa)) <0 ) goto fail; + bind_successful =1 ;
And this one needs to mark a flag explicitly, because we have no other visible way of knowing we need to do the unlink. Makes sense.
unix-socket.c int unix_stream_connect(const char *path) · line 119
Quoted 6 lines
unix-socket.c +5
-1-if (listen (fd,5 ) <0 ) +if (opts->listen_backlog_size >0 ) + backlog = opts->listen_backlog_size; +else + backlog =5 ; +if (listen (fd, backlog) <0 )
The default-to-5 is a bit funny here. We already set the default to 5 in UNIX_STREAM_LISTEN_OPTS_INIT. Should it be "0" there, so callers can treat that as "use the default", which we fill in here? It probably doesn't matter much in practice, but it seems cleaner to have only one spot with the magic number.
unix-socket.c int unix_stream_listen(const char *path) · line 134
Quoted 12 lines
unix-socket.c +4
-1@@ -114,7 +125,10 @@ int unix_stream_listen(const char *path) fail: saved_errno = errno;unix_sockaddr_cleanup (&ctx); -close (fd); +if (fd !=-1 ) +close (fd); +if (bind_successful) +unlink (path); errno = saved_errno;return -1 ; }
Should we unlink before closing? I usually try to undo actions in the reverse order that they were done. I thought at first it might even matter here, such that we'd atomically relinquish the name without having a moment where it still points to a closed socket (which might be less confusing to somebody else trying to connect). But I guess there will always be such a moment, because it's not like we would ever accept() or service a request.
-Peff
Re: [PATCH v2 12/14] unix-socket: add no-chdir option to unix_stream_listen()
In reply to Jeff Hostetler via GitGitGadgetQuoted 8 lines · Jeff Hostetler via GitGitGadget
From: Jeff Hostetler <[email protected]>
Calls to
chdir()are dangerous in a multi-threaded context. Ifunix_stream_listen()is given a socket pathname that is too big to fit in asockaddr_unstructure, it willchdir()to the parent directory of the requested socket pathname, create the socket using a relative pathname, and thenchdir()back. This is not thread-safe.
Quoted 5 lines
Add
disallow_chdirflag tostruct unix_sockaddr_contextand change all callers to pass an initialized context structure.Teach
unix_sockaddr_init()to not allow calls tochdir()when flag is set.
Makes sense, and it fits nicely into the options pattern you set up in the earlier patch.
Review
Quoted 10 lines
struct
unix_sockaddr_context{(quoted diff) +6
-0char *orig_dir; + unsigned int disallow_chdir:1; }; +#define UNIX_SOCKADDR_CONTEXT_INIT \ +{ \ + .orig_dir=NULL, \ + .disallow_chdir=0, \ +}
It is really just zero-initializing, so "{ 0 }" would be OK (I think we are relaxed about allowing 0 as NULL in initializers). But I don't mind it being written out (but do mind whitespace around the "=").
However, the point of unix_sockaddr_init() is that it's supposed to initialize the struct. And I don't think we need to carry disallow_chdir around; the cleanup function knows from orig_dir whether it's supposed to do any cleanup, so only the init function has to care. So would:
unix-socket.c +9 -2
@@ -36,16 +36,23 @@ static void unix_sockaddr_cleanup(struct unix_sockaddr_context *ctx) }static int unix_sockaddr_init (struct sockaddr_un *sa,const char *path, -struct unix_sockaddr_context *ctx) +struct unix_sockaddr_context *ctx, +int disallow_chdir) {int size =strlen (path) +1 ; ctx->orig_dir =NULL ;if (size >sizeof (sa->sun_path)) { -const char *slash =find_last_dir_sep (path); +const char *slash;const char *dir;struct strbuf cwd =STRBUF_INIT ; +if (disallow_chdir) { + errno =ENAMETOOLONG ; +return -1 ; + } + + slash =find_last_dir_sep (path);if (!slash) { errno =ENAMETOOLONG ;return -1 ;
make it more obvious? There are only two callers, and this is all file-local, so I don't mind adding the extra parameter there. And you would not need an initializer at all.
Review
Quoted 6 lines
#define
UNIX_STREAM_LISTEN_OPTS_INIT\
{ \(quoted diff) +1
-0.listen_backlog_size = 5, \ .force_unlink_before_bind = 1, \ + .disallow_chdir = 0, \ }
I don't know if we care, but some options are positive "do this unlink" and some are negative "do not do this chdir". Those could be made consistent (and flip the initializer value to keep the same defaults).
There is actually value in making struct defaults generally "0" unless we have reason not to, because callers sometimes zero-initialize without thinking about it. I doubt that would happen for this particular struct, and I'm deep into bike-shedding anyway, so I'm OK either way. But something like:
struct unix_stream_listen_opts_init {
int listen_backlog_size;
int disallow_unlink;
int disallow_chdir;};
would work with just a "{ 0 }" zero-initializer. :)
-Peff
Re: [PATCH v2 02/14] pkt-line: promote static buffer in packet_write_gently() to callers
In reply to Jeff KingQuoted 88 lines · Jeff King
pkt-line.c int packet_write_fmt_gently(int fd, const char *fmt, ...) · line 202
Quoted 7 lines · Jeff Hostetler via GitGitGadget
pkt-line.c +6
-1-static int packet_write_gently (const int fd_out,const char *buf,size_t size) +/* +* Use the provided scratch space to build a combined <hdr><buf> buffer +* and write it to the file descriptor (in one write if possible). +*/ +static int packet_write_gently (const int fd_out,const char *buf,size_t size, +struct packet_scratch_space *scratch)Thanks for addressing my stack space concern.
This solution does work (and I like wrapping it in a struct like this), though I have to wonder if we're not just punting on the thread issues in an ever-so-slight way with things like this:
pkt-line.c int packet_write_fmt_gently(int fd, const char *fmt, ...) · line 226
Quoted 8 lines
void packet_write(int fd_out, const char *buf, size_t size) {pkt-line.c +3
-1-if (packet_write_gently (fd_out, buf, size)) +static struct packet_scratch_space scratch; + +if (packet_write_gently (fd_out, buf, size, &scratch))die_errno (_ ("packet write failed" )); }Where we just moved it one step up the call stack.
pkt-line.c void packet_buf_write_len(struct strbuf *buf, const char *data, size_t len) · line 259
Quoted 7 lines
int write_packetized_from_fd(int fd_in, int fd_out) {pkt-line.c +5
-0+/* +* TODO We could save a memcpy() if we essentially inline +* TODO packet_write_gently() here and change the xread() +* TODO to pass &buf[4]. +*/ And comments like this make me wonder if the current crop of pktline functions are just mis-designed in the first place. There are two obvious directions here.
One, we can observe that the only reason we need the scratch space is to ship out the whole thing in a single
write():pkt-line.c int packet_write_fmt_gently(int fd, const char *fmt, ...) · line 216
Quoted 10 lines
[in
packet_write_gently]pkt-line.c +5
-3-set_packet_header (packet_write_buffer, packet_size); -memcpy (packet_write_buffer +4 , buf, size); -if (write_in_full (fd_out, packet_write_buffer, packet_size) <0 ) + +set_packet_header (scratch->buffer, packet_size); +memcpy (scratch->buffer +4 , buf, size); + +if (write_in_full (fd_out, scratch->buffer, packet_size) <0 )return error (_ ("packet write failed" ));Would it really be so bad to do:
char header[4]; set_packet_header(header, packet_size); if (write_in_full(fd_out, header, 4) < 0 || write_in_full(fd_out, buf, size) < 0) return error(...);I doubt that two syscalls is breaking the bank here, but if people are really concerned, using
writev()would be a much better solution. Obviously we can't rely on it being available everywhere, but it's quite easy to emulate with a wrapper (and I'd be happy punt on any writev stuff until somebody actually measures a difference).The other direction is that callers could be using a correctly-sized buffer in the first place. I.e., something like:
struct packet_buffer { char full_packet[LARGE_PACKET_MAX]; }; static inline char *packet_data(struct packet_buffer *pb) { return pb->full_packet + 4; }That lets people work with the oversized buffer in a natural-ish way that would be hard to get wrong, like:
memcpy(packet_data(pb), some_other_buf, len);(though if we wanted to go even further, we could provide accessors that actually do the writing and sanity-check the lengths; the downside is that I'm not sure how callers typically get the bytes into these bufs in the first place).
Quoted 5 lines
That's a much bigger change, of course, and I'd guess you'd much prefer to focus on the actual point of your series. ;)
-Peff
Yeah, I had all of those thoughts and debates in my head. I'm not sure there is a clear winner here. And I was trying to prevent this change from having a massive footprint and all that. The FSMonitor stuff is enough to worry about...
Personally, I like the 2 syscall model (for now at least and not mess with writev()). There are only 3 calls to packet_write_gently() and this fixes 2 of them without any local buffers. I might as well update the 1 caller of write_packetized_from_fd() to pass a buffer rather than have a static buffer while we're at it. Then all of those routines are fixed.
Let me see what that looks like.
Thanks
Jeff
Re: [PATCH v2 09/14] simple-ipc: add t/helper/test-simple-ipc and t0052
In reply to Jeff Hostetler via GitGitGadgetQuoted 20 lines · Jeff Hostetler via GitGitGadget
t/t0052-simple-ipc.sh new file +14
-0@@ -0,0 +1,129 @@ +#!/bin/sh + +test_description='simple command server' + +. ./test-lib.sh + +test-tool simple-ipc SUPPORTS_SIMPLE_IPC || { + skip_all='simple IPC not supported on this platform' + test_done +} + +stop_simple_IPC_server () { + test -n "$SIMPLE_IPC_PID" || return 0 +
t/t0052-simple-ipc.sh line 24
Quoted 10 lines
t/t0052-simple-ipc.sh new file +10
-0+ kill "$SIMPLE_IPC_PID" && + SIMPLE_IPC_PID= +} + +test_expect_success 'start simple command server' ' + { test-tool simple-ipc daemon --threads=8 & } && + SIMPLE_IPC_PID=$! && + test_atexit stop_simple_IPC_server && + + sleep 1 &&
This will certainly lead to occasional failures when the daemon takes longer than that mere 1 second delay under heavy load or in CI jobs.
Quoted 3 lines
t/t0052-simple-ipc.sh new file +3
-0+ + test-tool simple-ipc is-active +'
Re: [PATCH v2 02/14] pkt-line: promote static buffer in packet_write_gently() to callers
In reply to Jeff KingHi Peff,
Quoted 15 lines · Jeff King
pkt-line.c int packet_write_fmt_gently(int fd, const char *fmt, ...) · line 216
Quoted 10 lines · Jeff Hostetler via GitGitGadget
[in
packet_write_gently]pkt-line.c +5
-3-set_packet_header (packet_write_buffer, packet_size); -memcpy (packet_write_buffer +4 , buf, size); -if (write_in_full (fd_out, packet_write_buffer, packet_size) <0 ) + +set_packet_header (scratch->buffer, packet_size); +memcpy (scratch->buffer +4 , buf, size); + +if (write_in_full (fd_out, scratch->buffer, packet_size) <0 )return error (_ ("packet write failed" ));Would it really be so bad to do:
Quoted 5 lines
char header[4];
set_packet_header(header, packet_size);
if (write_in_full(fd_out, header, 4) < 0 ||
write_in_full(fd_out, buf, size) < 0)
return error(...);There must have been a reason why the original code went out of its way to copy the data. At least that's what I _assume_.
I could see, for example, that these extra round-trips just for the header, really have a negative impact on network operations.
Quoted 2 lines
I doubt that two syscalls is breaking the bank here, but if people are really concerned, using
writev()would be a much better solution.
No, because there is no equivalent for that on Windows. And since Windows is the primary target of our Simple IPC/FSMonitor work, that would break the bank.
Quoted 7 lines
Obviously we can't rely on it being available everywhere, but it's quite easy to emulate with a wrapper (and I'd be happy punt on any writev stuff until somebody actually measures a difference).
The other direction is that callers could be using a correctly-sized buffer in the first place. I.e., something like:
Quoted 7 lines
struct packet_buffer { char full_packet[LARGE_PACKET_MAX]; }; static inline char *packet_data(struct packet_buffer *pb) { return pb->full_packet + 4;}
Or we change it to
struct packet_buffer {
char count[4];
char payload[LARGE_PACKET_MAX - 4];
};and then ask the callers to allocate one of those beauties Not sure how well we can guarantee that the compiler won't pad this, though.
And then there is write_packetized_from_buf() whose src parameter can come from convert_to_git() that _definitely_ would not be of the desired form.
So I guess if we can get away with the 2-syscall version, that's kind of better than that.
Ciao,
Dscho
Quoted 16 lines
That lets people work with the oversized buffer in a natural-ish way that would be hard to get wrong, like:
memcpy(packet_data(pb), some_other_buf, len);(though if we wanted to go even further, we could provide accessors that actually do the writing and sanity-check the lengths; the downside is that I'm not sure how callers typically get the bytes into these bufs in the first place).
That's a much bigger change, of course, and I'd guess you'd much prefer to focus on the actual point of your series. ;)
-Peff
Re: [PATCH v2 04/14] pkt-line: optionally skip the flush packet in write_packetized_from_buf()
In reply to Jeff KingHi Peff,
Quoted 12 lines · Jeff King
Quoted 9 lines · Johannes Schindelin via GitGitGadget
From: Johannes Schindelin <[email protected]>
This function currently has only one caller:
apply_multi_file_filter()inconvert.c. That caller wants a flush packet to be written after writing the payload.However, we are about to introduce a user that wants to write many packets before a final flush packet, so let's extend this function to prepare for that scenario.
Quoted 6 lines
I think this is a sign that the function is not very well-designed in the first place. It seems like the code would be easier to understand overall if that caller just explicitly did the flush itself. It even already does so in other cases!
Something like (untested):
Fine by me.
Thanks,
Dscho
Quoted 45 lines
convert.c | 4 ++++ pkt-line.c | 4 ---- 2 files changed, 4 insertions(+), 4 deletions(-)convert.c +4
-0@@ -890,6 +890,10 @@ static int apply_multi_file_filter(const char *path, const char *src, size_t lenif (err) goto done; + err =packet_flush_gently (process->in); +if (err) + goto done; + err =subprocess_read_status (process->out, &filter_status);if (err) goto done;pkt-line.c +0
-5@@ -256,8 +256,6 @@ int write_packetized_from_fd(int fd_in, int fd_out)break ; err =packet_write_gently (fd_out, buf, bytes_to_write); } -if (!err) - err =packet_flush_gently (fd_out);return err; }@@ -277,8 +275,6 @@ int write_packetized_from_buf(const char *src_in, size_t len, int fd_out) err =packet_write_gently (fd_out, src_in + bytes_written, bytes_to_write); bytes_written += bytes_to_write; } -if (!err) - err =packet_flush_gently (fd_out);return err; } -Peff
Hi Junio & Jeff,
Quoted 9 lines · Jeff Hostetler
Quoted 8 lines · Junio C Hamano
Quoted 5 lines · Jeff Hostetler via GitGitGadget
Here is version 2 of my "Simple IPC" series and addresses the following review comments:
...
Junio C Hamano (1):
ci/install-depends: attempt to fix "brew cask" stuff
Quoted 6 lines
Quoted 2 lines
Huh?
Sorry. I had to prepend that one to the patch series to get the CI builds to run. I've been working rebased against "v2.30.0" and GitGitGadget references "master".
The idea being that we want to be able to merge this branch as-is into Git for Windows (and also into microsoft/git), and therefore do not want to base it on a later commit that is not reachable from git-for-windows/git's main branch.
Maybe it is time to merge jc/macos-install-dependencies-fix down to maint? Then we could base Simple IPC/FSMonitor on maint instead, and would still have the benefit we want.
Ciao,
Dscho
Re: [PATCH v2 09/14] simple-ipc: add t/helper/test-simple-ipc and t0052
In reply to SZEDER GáborQuoted 9 lines · SZEDER Gábor
t/t0052-simple-ipc.sh line 24
Quoted 6 lines
t/t0052-simple-ipc.sh new file +6
-0+test_expect_success 'start simple command server' ' + { test-tool simple-ipc daemon --threads=8 & } && + SIMPLE_IPC_PID=$! && + test_atexit stop_simple_IPC_server && + + sleep 1 &&This will certainly lead to occasional failures when the daemon takes longer than that mere 1 second delay under heavy load or in CI jobs.
Yeah. The robust thing is to have the server indicate when it's ready to receive requests. There's some prior art in t/lib-git-daemon.sh using a fifo to get a line to the caller. It's ugly, but AFAIK pretty bulletproof.
-Peff
Re: [PATCH v2 02/14] pkt-line: promote static buffer in packet_write_gently() to callers
In reply to Johannes SchindelinQuoted 10 lines · Johannes Schindelin
Quoted 7 lines
Would it really be so bad to do:
char header[4]; set_packet_header(header, packet_size); if (write_in_full(fd_out, header, 4) < 0 || write_in_full(fd_out, buf, size) < 0) return error(...);There must have been a reason why the original code went out of its way to copy the data. At least that's what I _assume_.
Having looked at the history, including the original mailing list threads, it doesn't seem to be.
Quoted 2 lines
I could see, for example, that these extra round-trips just for the header, really have a negative impact on network operations.
Keep in mind these won't be network round-trips. They're just syscall round-trips. The OS would keep writing without an ACK while filling a TCP window. The worst case may be an extra packet on the wire, though the OS may end up coalescing the writes into a single packet anyway.
Quoted 6 lines
Quoted 2 lines
I doubt that two syscalls is breaking the bank here, but if people are really concerned, using
writev()would be a much better solution.No, because there is no equivalent for that on Windows. And since Windows is the primary target of our Simple IPC/FSMonitor work, that would break the bank.
Are you concerned about the performance implications, or just portability? Falling back to two writes (and wrapping that in a function) would be easy for the latter. For the former, there's WSASend, but I have no idea what kind of difficulties/caveats we might run into.
Quoted 13 lines
Quoted 10 lines
The other direction is that callers could be using a correctly-sized buffer in the first place. I.e., something like:
struct packet_buffer { char full_packet[LARGE_PACKET_MAX]; }; static inline char *packet_data(struct packet_buffer *pb) { return pb->full_packet + 4;}
Or we change it to
Quoted 8 lines
struct packet_buffer { char count[4]; char payload[LARGE_PACKET_MAX - 4]; };and then ask the callers to allocate one of those beauties Not sure how well we can guarantee that the compiler won't pad this, though.
Yeah, I almost suggested the same, but wasn't sure about padding. I think the standard allows there to be arbitrary padding between the two, so it's really up to the ABI to define. I'd be surprised if this struct is a problem in practice (we already have some structs which assume 4-byte alignment, and nobody seems to have complained).
Quoted 3 lines
And then there is
write_packetized_from_buf()whosesrcparameter can come fromconvert_to_git()that _definitely_ would not be of the desired form.
Yep. It really does need to either use two writes or to copy, because it's slicing up a much larger buffer (it wouldn't be the end of the world for it to allocate a single LARGE_PACKET_MAX heap buffer for the duration of its run, though).
Quoted 2 lines
So I guess if we can get away with the 2-syscall version, that's kind of better than that.
I do prefer it, because then the whole thing just becomes an implementation detail that callers don't need to care about.
-Peff
Quoted 8 lines · Johannes Schindelin
The idea being that we want to be able to merge this branch as-is into Git for Windows (and also into microsoft/git), and therefore do not want to base it on a later commit that is not reachable from git-for-windows/git's
mainbranch.Maybe it is time to merge
jc/macos-install-dependencies-fixdown tomaint? Then we could base Simple IPC/FSMonitor onmaintinstead, and would still have the benefit we want.
Now you mention it, if we ever have an update to the current 'maint' branch, we'd trigger this known failure in macOS build without a good reason, even though we have a fix that has been in use on the 'master' and above.
I definitely qshould merge the jc/macos-install-dependencies-fix topic down to 'maint' now.
Are there other topics that deserve to be in 'maint' that are "obviously correct" people can think of?
Thanks.
candidate branches for `maint`, was Re: [PATCH v2 00/14] Simple IPC Mechanism
Hi Junio,
Quoted 16 lines · Junio C Hamano
Quoted 8 lines · Johannes Schindelin
The idea being that we want to be able to merge this branch as-is into Git for Windows (and also into microsoft/git), and therefore do not want to base it on a later commit that is not reachable from git-for-windows/git's
mainbranch.Maybe it is time to merge
jc/macos-install-dependencies-fixdown tomaint? Then we could base Simple IPC/FSMonitor onmaintinstead, and would still have the benefit we want.Now you mention it, if we ever have an update to the current 'maint' branch, we'd trigger this known failure in macOS build without a good reason, even though we have a fix that has been in use on the 'master' and above.
Quoted 5 lines
I definitely qshould merge the jc/macos-install-dependencies-fix topic down to 'maint' now.
Are there other topics that deserve to be in 'maint' that are "obviously correct" people can think of?
I looked over the branches merged into master that are not in maint, and from a cursory look, these seem to be good candidates:
- pk/subsub-fetch-fix-take-2
- rs/rebase-commit-validation
- en/stash-apply-sparse-checkout
- ds/for-each-repo-noopfix
- pb/mergetool-tool-help-fix
- jk/t5516-deflake
- jc/macos-install-dependencies-fix
- jk/forbid-lf-in-git-url
- jk/log-cherry-pick-duplicate-patches
- js/skip-dashed-built-ins-from-config-mak
From a cursory look, it might seem as if ds/maintenance-prefetch-cleanup would also be a good candidate, but it is not based on maint, although it _does_ appear to fix an issue introduced in v2.30.0-rc0~23^2~8.
There is another candidate that I am not _quite_ sure about:
dl/p4-encode-after-kw-expansion. It _seems_ as if it would be good to apply on the maintenance train, but I am uncertain how important a bug fix it is.
There are also a couple test updates that might be nice to have in maint:
- nk/perf-fsmonitor-cleanup
- mt/t4129-with-setgid-dir
- ad/t4129-setfacl-target-fix
Finally, there are documentation updates that I would probably merge, if I was tasked with updating maint:
- ta/doc-typofix
- pb/doc-modules-git-work-tree-typofix
- jc/sign-off
- vv/send-email-with-less-secure-apps-access
- ug/doc-lose-dircache
- ab/gettext-charset-comment-fix
- bc/doc-status-short
- tb/local-clone-race-doc
- ab/fsck-doc-fix
- jt/packfile-as-uri-doc
Ciao,
Dscho
Re: [PATCH v2 04/14] pkt-line: optionally skip the flush packet in write_packetized_from_buf()
In reply to Jeff KingQuoted 8 lines · Jeff King
Quoted 6 lines · Johannes Schindelin via GitGitGadget
From: Johannes Schindelin <[email protected]>
This function currently has only one caller:
apply_multi_file_filter()inconvert.c. That caller wants a flush packet to be written after writing the payload.
Quoted 9 lines
Quoted 3 lines
However, we are about to introduce a user that wants to write many packets before a final flush packet, so let's extend this function to prepare for that scenario.
I think this is a sign that the function is not very well-designed in the first place. It seems like the code would be easier to understand overall if that caller just explicitly did the flush itself. It even already does so in other cases!
I agree. I'll move flush to the caller and rename the write packetized function slightly to guard against new callers assuming the old behavior during the transition.
Jeff
Quoted 46 lines
Something like (untested):
convert.c | 4 ++++ pkt-line.c | 4 ---- 2 files changed, 4 insertions(+), 4 deletions(-)convert.c +4
-0@@ -890,6 +890,10 @@ static int apply_multi_file_filter(const char *path, const char *src, size_t lenif (err) goto done; + err =packet_flush_gently (process->in); +if (err) + goto done; + err =subprocess_read_status (process->out, &filter_status);if (err) goto done;pkt-line.c +0
-5@@ -256,8 +256,6 @@ int write_packetized_from_fd(int fd_in, int fd_out)break ; err =packet_write_gently (fd_out, buf, bytes_to_write); } -if (!err) - err =packet_flush_gently (fd_out);return err; }@@ -277,8 +275,6 @@ int write_packetized_from_buf(const char *src_in, size_t len, int fd_out) err =packet_write_gently (fd_out, src_in + bytes_written, bytes_to_write); bytes_written += bytes_to_write; } -if (!err) - err =packet_flush_gently (fd_out);return err; } -Peff
Re: [PATCH v2 09/14] simple-ipc: add t/helper/test-simple-ipc and t0052
In reply to Jeff Hostetler via GitGitGadgett/t0052-simple-ipc.sh line 127
Quoted 10 lines · Jeff Hostetler via GitGitGadget
Create unit tests for "simple-ipc". These are currently only enabled on Windows.
t/t0052-simple-ipc.sh +5
-0+test_expect_success '`quit` works' ' + test-tool simple-ipc send quit && + test_must_fail test-tool simple-ipc is-active && + test_must_fail test-tool simple-ipc send ping +'
This test is flaky as well, and it did actually fail in CI:
expecting success of 0052.9 'quit works':
test-tool simple-ipc send quit && test_must_fail test-tool simple-ipc is-active && test_must_fail test-tool simple-ipc send ping
+test-tool simple-ipc send quit
+test_must_fail test-tool simple-ipc is-active
test_must_fail: command succeeded: test-tool simple-ipc is-active
error: last command exited with $?=1
not ok 9 - quit works
Quoted 5 lines
t/t0052-simple-ipc.sh +2
-0+ +test_doneSignature · 1 line
gitgitgadget
Re: candidate branches for `maint`, was Re: [PATCH v2 00/14] Simple IPC Mechanism
In reply to Johannes SchindelinQuoted 12 lines · Johannes Schindelin
Quoted 2 lines
Are there other topics that deserve to be in 'maint' that are "obviously correct" people can think of?
I looked over the branches merged into
masterthat are not inmaint, and from a cursory look, these seem to be good candidates:
...
There are also a couple test updates that might be nice to have inmaint:
...
Finally, there are documentation updates that I would probably merge, if I was tasked with updatingmaint:
...
Your list more or less matches what the ML (merge later) script on the todo/ branch produces when it is fed the RelNotes (the script just greps for "merge laster to maint" comments and shows the result in way a bit easier to use for me).
The ones that are in RelNotes but not in your list are
ab/branch-sort # 7 (11 days ago)
ar/t6016-modernise # 1 (3 weeks ago) dl/p4-encode-after-kw-expansion # 1 (3 weeks ago) fc/t6030-bisect-reset-removes-auxiliary-files # 1 (4 weeks ago) ma/doc-pack-format-varint-for-sizes # 1 (3 weeks ago) ma/more-opaque-lock-file # 5 (11 days ago) ma/t1300-cleanup # 3 (3 weeks ago) zh/arg-help-format # 2 (3 weeks ago)
and I think all of them are safe to merge down.
Thanks for being an independent source I can rely on to sanity check what is in RelNotes. Very much appreciated.
Re: [PATCH v2 11/14] unix-socket: add options to unix_stream_listen()
In reply to Jeff KingQuoted 10 lines · Jeff King
Quoted 7 lines · Jeff Hostetler via GitGitGadget
From: Jeff Hostetler <[email protected]>
Update
unix_stream_listen()to take an options structure to override default behaviors. This includes the size of thelisten()backlog and whether it should always unlink the socket file before trying to create a new one. Also eliminate calls todie()if it cannot create a socket.
Quoted 7 lines
I sent a follow-up on the previous patch, but I think this part about the
die()should be folded in there.Likewise I think it would probably be easier to follow if we added the backlog parameter and the unlink options in separate patches. The backlog thing is small, but the unlink part is subtle and requires explanation. That's a good sign it might do better in its own commit.
Yes, that helped having them in 2 patches each with 1 concern.
Quoted 18 lines
Quoted 12 lines
Normally,
unix_stream_listen()always tries tounlink()the socket-path before callingbind(). If there is an existing server/daemon already bound and listening on that socket-path, ourunlink()would have the effect of disassociating the existing server's bound-socket-fd from the socket-path without notifying the existing server. The existing server could continue to service existing connections (accepted-socket-fd's), but would not receive any futher new connections (since clients rendezvous via the socket-path). The existing server would effectively be offline but yet appear to be active.Quoted 5 lines
Furthermore,
unix_stream_listen()creates an opportunity for a brief race condition for connecting clients if they try to connect in the interval between the forcedunlink()and the subsequentbind()(which recreates the socket-path that is bound to a new socket-fd in the current process).
Quoted 4 lines
OK. I'm still not sure of the endgame here for writing non-racy code to establish the socket (which is going to require either some atomic renaming or some dot-locking in the caller). But it's plausible to me that this option will be a useful primitive.
In part 14/14 in ipc-unix-sockets.c:create_listener_socket() I have code in the calling layer to (try to) handle both the startup races and basic collisions with existing long-running servers already using the socket.
But you're right, it might be good to revisit that as a primitive at this layer. We only have 1 other caller right now and I don't know enough about credential-cache--daemon to know if it would benefit from this or not.
Quoted 35 lines
The implementation looks correct, though here are a few small observations/questions/nits:
Quoted 6 lines
unix-socket.c +3
-2-int unix_stream_listen (const char *path) +int unix_stream_listen (const char *path, +const struct unix_stream_listen_opts *opts) { -int fd, saved_errno; +int fd =-1 ;unix-socket.c int unix_stream_connect(const char *path) · line 102
Quoted 10 lines
unix-socket.c +3
-2+int saved_errno; +int bind_successful =0 ; +int backlog;struct sockaddr_un sa;struct unix_sockaddr_context ctx; -unlink (path); -if (unix_sockaddr_init (&sa, path, &ctx) <0 )return -1 ;We can return directly here, because we know there is nothing to clean up. Which I thought mean that here...
unix-socket.c int unix_stream_connect(const char *path) · line 106
Quoted 5 lines
unix-socket.c +2
-1+ fd =socket (AF_UNIX ,SOCK_STREAM ,0 );if (fd <0 ) -die_errno ("unable to create socket" ); + goto fail;...we are in the same boat. We did not create a socket, so we can just return. That makes our cleanup code a bit simpler. But we can't do that, because
unix_sockaddr_init()may have done things that need cleaning up (like chdir). So what you have here is correct.
Quoted 2 lines
IMHO that is all the more reason to push this (and the similar code in
unix_stream_connect()added in patch 13) into the previous patch.
Agreed.
Quoted 11 lines
unix-socket.c int unix_stream_connect(const char *path) · line 113
Quoted 7 lines
unix-socket.c +3
-0+if (opts->force_unlink_before_bind) +unlink (path);if (bind (fd, (struct sockaddr *)&sa,sizeof (sa)) <0 ) goto fail; + bind_successful =1 ;And this one needs to mark a flag explicitly, because we have no other visible way of knowing we need to do the unlink. Makes sense.
Quoted 12 lines
unix-socket.c int unix_stream_connect(const char *path) · line 119
Quoted 6 lines
unix-socket.c +5
-1-if (listen (fd,5 ) <0 ) +if (opts->listen_backlog_size >0 ) + backlog = opts->listen_backlog_size; +else + backlog =5 ; +if (listen (fd, backlog) <0 )The default-to-5 is a bit funny here. We already set the default to 5 in
UNIX_STREAM_LISTEN_OPTS_INIT. Should it be "0" there, so callers can treat that as "use the default", which we fill in here? It probably doesn't matter much in practice, but it seems cleaner to have only one spot with the magic number.
I'll refactor this a bit.
Quoted 14 lines
unix-socket.c int unix_stream_listen(const char *path) · line 134
Quoted 13 lines
unix-socket.c +4
-1@@ -114,7 +125,10 @@ int unix_stream_listen(const char *path) fail: saved_errno = errno;unix_sockaddr_cleanup (&ctx); -close (fd); +if (fd !=-1 ) +close (fd); +if (bind_successful) +unlink (path); errno = saved_errno;return -1 ; }
Quoted 7 lines
Should we unlink before closing? I usually try to undo actions in the reverse order that they were done. I thought at first it might even matter here, such that we'd atomically relinquish the name without having a moment where it still points to a closed socket (which might be less confusing to somebody else trying to connect). But I guess there will always be such a moment, because it's not like we would ever
accept()or service a request.
I'm not sure it matters, but it does look better to unwind things in reverse order. And yes, unlinking first is a little bit safer.
Quoted 3 lines
-Peff
Re: [PATCH v2 09/14] simple-ipc: add t/helper/test-simple-ipc and t0052
In reply to SZEDER GáborQuoted 26 lines · SZEDER Gábor
Quoted 25 lines · Jeff Hostetler via GitGitGadget
t/t0052-simple-ipc.sh new file +19
-0@@ -0,0 +1,129 @@ +#!/bin/sh + +test_description='simple command server' + +. ./test-lib.sh + +test-tool simple-ipc SUPPORTS_SIMPLE_IPC || { + skip_all='simple IPC not supported on this platform' + test_done +} + +stop_simple_IPC_server () { + test -n "$SIMPLE_IPC_PID" || return 0 + + kill "$SIMPLE_IPC_PID" && + SIMPLE_IPC_PID= +} + +test_expect_success 'start simple command server' '
Quoted 8 lines
t/t0052-simple-ipc.sh line 24
Quoted 5 lines
t/t0052-simple-ipc.sh new file +5
-0+ { test-tool simple-ipc daemon --threads=8 & } && + SIMPLE_IPC_PID=$! && + test_atexit stop_simple_IPC_server && + + sleep 1 &&This will certainly lead to occasional failures when the daemon takes longer than that mere 1 second delay under heavy load or in CI jobs.
Good point. Thanks!
Quoted 4 lines
Quoted 4 lines
t/t0052-simple-ipc.sh new file +3
-0+ + test-tool simple-ipc is-active +'
Re: [PATCH v2 11/14] unix-socket: add options to unix_stream_listen()
In reply to Jeff HostetlerQuoted 9 lines · Jeff Hostetler
Quoted 4 lines
OK. I'm still not sure of the endgame here for writing non-racy code to establish the socket (which is going to require either some atomic renaming or some dot-locking in the caller). But it's plausible to me that this option will be a useful primitive.
In part 14/14 in
ipc-unix-sockets.c:create_listener_socket()I have code in the calling layer to (try to) handle both the startup races and basic collisions with existing long-running servers already using the socket.
There you make a temp socket and then try to rename it into place. But because rename() overwrites the destination, it still seems like two creating processes can race each other. Something like:
0. There's no "foo" socket (or maybe there is a stale one that nobody's listening on).
1. Process A wants to become the listener. So it creates foo.A.
2. Process B likewise. It creates foo.B.
3. Process A renames foo.A to foo. It believes it will now service clients.
4. Process B renames foo.B to foo. Now process A is stranded but doesn't realize it.
I.e., I don't think this is much different than an unlink+create strategy. You've eliminated the window where a process C shows up during steps 3 and 4 and sees no socket (because somebody else is in the midst of a non-atomic unlink+create operation). But there's no atomicity between the "ping the socket" and "create the socket" steps.
Quoted 4 lines
But you're right, it might be good to revisit that as a primitive at this layer. We only have 1 other caller right now and I don't know enough about
credential-cache--daemonto know if it would benefit from this or not.
Yeah, having seen patch 14, it looks like your only new caller always sets the new unlink option to 1. So it might not be worth making it optional if you don't need it (especially because the rename trick, assuming it's portable, is superior to unlink+create; and you'd always be fine with an unlink on the temp socket).
The call in credential-cache--daemon is definitely racy. It's pretty much the same thing: it pings the socket to see if it's alive, but is still susceptible to the problem above. I was was never too concerned about it, since the whole point of the daemon is to hang around until its contents expire. If it loses the race and nobody contacts it, the worst case is it waits 30 seconds for somebody to give it data before exiting. It would benefit slightly from switching to the rename strategy, but the bigger race would remain.
-Peff
Re: [PATCH v2 11/14] unix-socket: add options to unix_stream_listen()
Quoted 21 lines · Jeff King
Quoted 9 lines · Jeff Hostetler
Quoted 4 lines
OK. I'm still not sure of the endgame here for writing non-racy code to establish the socket (which is going to require either some atomic renaming or some dot-locking in the caller). But it's plausible to me that this option will be a useful primitive.
In part 14/14 in
ipc-unix-sockets.c:create_listener_socket()I have code in the calling layer to (try to) handle both the startup races and basic collisions with existing long-running servers already using the socket.There you make a temp socket and then try to rename it into place. But because
rename()overwrites the destination, it still seems like two creating processes can race each other. Something like:0. There's no "foo" socket (or maybe there is a stale one that nobody's listening on).
1. Process A wants to become the listener. So it creates foo.A.
Quoted 8 lines
2. Process B likewise. It creates foo.B.
3. Process A renames foo.A to foo. It believes it will now service clients.
4. Process B renames foo.B to foo. Now process A is stranded but doesn't realize it.
Yeah, in my version two processes could still create uniquely named sockets and then do the rename trick. But they capture the inode number of the socket before they do that. They periodically lstat the socket to see if the inode number has changed and if so, assume it has been stolen from them. (A bit of a hack, I admit.)
And I was assuming that 2 servers starting at about the same time are effectively equivalent -- it doesn't matter which one dies, since they both should have the same amount of cached state. Unlike the case where a long-running server (with lots of state) is replaced by a newcomer.
Quoted 11 lines
I.e., I don't think this is much different than an unlink+create strategy. You've eliminated the window where a process C shows up during steps 3 and 4 and sees no socket (because somebody else is in the midst of a non-atomic unlink+create operation). But there's no atomicity between the "ping the socket" and "create the socket" steps.
Quoted 4 lines
But you're right, it might be good to revisit that as a primitive at this layer. We only have 1 other caller right now and I don't know enough about
credential-cache--daemonto know if it would benefit from this or not.
Quoted 5 lines
Yeah, having seen patch 14, it looks like your only new caller always sets the new unlink option to 1. So it might not be worth making it optional if you don't need it (especially because the rename trick, assuming it's portable, is superior to unlink+create; and you'd always be fine with an unlink on the temp socket).
I am wondering if we can use the .LOCK file magic to our advantage here (in sort of an off-label use). If we have the server create a lockfile "<path>.LOCK" and if successful leave it open/locked for the life of the server (rather than immediately renaming it onto <path>) and let the normal shutdown code rollback/delete the lockfile in the cleanup/atexit.
If the server successfully creates the lockfile, then unlink+create the socket at <path>.
That would give us the unique/exclusive creation (on the lock) that we need. Then wrap that with all the edge case cleanup code to create/delete/manage the peer socket. Basically if the lock exists, there should be a live server listening to the socket (unless there was a crash...).
And yes, then I don't think I need the preserve_existing bit in the opts struct.
Quoted 12 lines
The call in credential-cache--daemon is definitely racy. It's pretty much the same thing: it pings the socket to see if it's alive, but is still susceptible to the problem above. I was was never too concerned about it, since the whole point of the daemon is to hang around until its contents expire. If it loses the race and nobody contacts it, the worst case is it waits 30 seconds for somebody to give it data before exiting. It would benefit slightly from switching to the rename strategy, but the bigger race would remain.
-Peff
Re: [PATCH v2 11/14] unix-socket: add options to unix_stream_listen()
Quoted 5 lines · Jeff Hostetler
Yeah, in my version two processes could still create uniquely named sockets and then do the rename trick. But they capture the inode number of the socket before they do that. They periodically lstat the socket to see if the inode number has changed and if so, assume it has been stolen from them. (A bit of a hack, I admit.)
OK, that makes more sense. I saw the mention of the inode stuff in a comment, but I didn't see it in the code (I guess if it's a periodic check it's not in that initial socket creation function).
Quoted 5 lines
And I was assuming that 2 servers starting at about the same time are effectively equivalent -- it doesn't matter which one dies, since they both should have the same amount of cached state. Unlike the case where a long-running server (with lots of state) is replaced by a newcomer.
Yeah, I agree with that notion in general. I do think it would be easier to reason about if the creation were truly race-proof (probably with a dot-lock; see below), rather than the later "check if we got replaced" thing. OTOH, that "check" strategy covers a variety of cases (including that somebody tried to ping us, decided we weren't alive due to a timeout or some other system reason, and then replaced our socket).
Another strategy there could be having the daemon just decide to quit if nobody contacts it for N time units. It is, after all, a cache. Even if nobody replaces the socket, it probably makes sense to eventually decide that the memory we're holding isn't going to good use.
Quoted 9 lines
I am wondering if we can use the .LOCK file magic to our advantage here (in sort of an off-label use). If we have the server create a lockfile "<path>.LOCK" and if successful leave it open/locked for the life of the server (rather than immediately renaming it onto <path>) and let the normal shutdown code rollback/delete the lockfile in the cleanup/atexit.
If the server successfully creates the lockfile, then unlink+create the socket at <path>.
I don't even think this is off-label. Though the normal use is for the .lock file to get renamed into place as the official file, there are a few other places where we use it solely for mutual exclusion. You just always end with rollback_lock_file(), and never "commit" it.
So something like:
1. Optimistically see if socket "foo" is present and accepting connections.
2. If not, then take "foo.lock". If somebody else is holding it, loop with a timeout waiting for them to come alive.
3. Assuming we got the lock, then either unlink+create the socket as "foo", or rename-into-place. I don't think it matters that much which.
4. Rollback "foo.lock", unlinking it.
Then one process wins the lock and creates the socket, while any simultaneous creators spin in step 2, and eventually connect to the winner.
Quoted 5 lines
That would give us the unique/exclusive creation (on the lock) that we need. Then wrap that with all the edge case cleanup code to create/delete/manage the peer socket. Basically if the lock exists, there should be a live server listening to the socket (unless there was a crash...).
I think you'd want to delete the lock as soon as you're done with the setup. That reduces the chances that a dead server (e.g., killed by a power outage without the chance to clean up after itself) leaves a stale lock sitting around.
-Peff
Re: [PATCH v2 11/14] unix-socket: add options to unix_stream_listen()
Quoted 8 lines · Jeff King
Quoted 5 lines · Jeff Hostetler
Yeah, in my version two processes could still create uniquely named sockets and then do the rename trick. But they capture the inode number of the socket before they do that. They periodically lstat the socket to see if the inode number has changed and if so, assume it has been stolen from them. (A bit of a hack, I admit.)
Quoted 4 lines
OK, that makes more sense. I saw the mention of the inode stuff in a comment, but I didn't see it in the code (I guess if it's a periodic check it's not in that initial socket creation function).
Yeah, there's a very slow poll(2) loop in the listen/accept thread that watches for that and new connections (and quit messages).
Quoted 13 lines
Quoted 5 lines
And I was assuming that 2 servers starting at about the same time are effectively equivalent -- it doesn't matter which one dies, since they both should have the same amount of cached state. Unlike the case where a long-running server (with lots of state) is replaced by a newcomer.
Yeah, I agree with that notion in general. I do think it would be easier to reason about if the creation were truly race-proof (probably with a dot-lock; see below), rather than the later "check if we got replaced" thing. OTOH, that "check" strategy covers a variety of cases (including that somebody tried to ping us, decided we weren't alive due to a timeout or some other system reason, and then replaced our socket).
Quoted 4 lines
Another strategy there could be having the daemon just decide to quit if nobody contacts it for N time units. It is, after all, a cache. Even if nobody replaces the socket, it probably makes sense to eventually decide that the memory we're holding isn't going to good use.
I have the poll(2) loop set to recheck the inode for theft every 60 seconds (randomly chosen).
Assuming the socket isn't stolen, I want to leave any thoughts of an auto-shutdown to the application layer above it. My next patch series will use this ipc mechanism to build a FSMonitor daemon that will watch the filesystem for changes and then be able to quickly respond to a git status, so it is important that it be allowed to run without any clients for a while (such a during a build). Yes, memory concerns are important, so I do want it to auto-shutdown if the socket is stolen (or the workdir is deleted).
Quoted 40 lines
Quoted 10 lines
I am wondering if we can use the .LOCK file magic to our advantage here (in sort of an off-label use). If we have the server create a lockfile "<path>.LOCK" and if successful leave it open/locked for the life of the server (rather than immediately renaming it onto <path>) and let the normal shutdown code rollback/delete the lockfile in the cleanup/atexit.
If the server successfully creates the lockfile, then unlink+create the socket at <path>.
I don't even think this is off-label. Though the normal use is for the .lock file to get renamed into place as the official file, there are a few other places where we use it solely for mutual exclusion. You just always end with
rollback_lock_file(), and never "commit" it.So something like:
1. Optimistically see if socket "foo" is present and accepting connections.
2. If not, then take "foo.lock". If somebody else is holding it, loop with a timeout waiting for them to come alive.
3. Assuming we got the lock, then either unlink+create the socket as "foo", or rename-into-place. I don't think it matters that much which.
4. Rollback "foo.lock", unlinking it.
Then one process wins the lock and creates the socket, while any simultaneous creators spin in step 2, and eventually connect to the winner.
Quoted 5 lines
That would give us the unique/exclusive creation (on the lock) that we need. Then wrap that with all the edge case cleanup code to create/delete/manage the peer socket. Basically if the lock exists, there should be a live server listening to the socket (unless there was a crash...).
Quoted 4 lines
I think you'd want to delete the lock as soon as you're done with the setup. That reduces the chances that a dead server (e.g., killed by a power outage without the chance to clean up after itself) leaves a stale lock sitting around.
Thanks this helps. I've got a version now that is a slight variation on what you have here that seems to work nicely and has the short-lived lock file. I'll post this shortly.
Jeff
Re: [PATCH v2 06/14] pkt-line: accept additional options in read_packetized_to_strbuf()
In reply to Johannes Schindelin via GitGitGadgetQuoted 14 lines · Johannes Schindelin via GitGitGadget
pkt-line.c +1
-1@@ -461,7 +461,7 @@ char *packet_read_line_buf(char **src, size_t *src_len, int *dst_len)return packet_read_line_generic (-1 , src, src_len, dst_len); } -ssize_t read_packetized_to_strbuf (int fd_in,struct strbuf *sb_out) +ssize_t read_packetized_to_strbuf (int fd_in,struct strbuf *sb_out,int options) {int packet_len;
pkt-line.c ssize_t read_packetized_to_strbuf(int fd_in, struct strbuf *sb_out) · line 480
Quoted 5 lines
pkt-line.c +1
-1@@ -477,7 +477,7 @@ ssize_t read_packetized_to_strbuf(int fd_in, struct strbuf *sb_out)* that there is already room for the extra byte. */ sb_out->buf + sb_out->len,LARGE_PACKET_DATA_MAX +1 , - PACKET_READ_GENTLE_ON_EOF); + options | PACKET_READ_GENTLE_ON_EOF);
This feels a little magical to me. Since read_packetized_to_strbuf only has the one caller you mention, why not have the caller pass all of the options (including PACKET_READ_GENTLE_ON_EOF)?
Quoted 8 lines
pkt-line.c +0
-0if (packet_len <=0 )break ; sb_out->len += packet_len;pkt-line.h +0
-0
pkt-line.h char *packet_read_line_buf(char **src_buf, size_t *src_len, int *size); · line 150
Quoted 6 lines
pkt-line.h +3
-0@@ -145,8 +145,12 @@ char *packet_read_line_buf(char **src_buf, size_t *src_len, int *size);/* * Reads a stream of variable sized packets until a flush packet is detected. +* +* The options are augmented by PACKET_READ_GENTLE_ON_EOF and passed to +* packet_read.
Obviously this comment will need updating if you take my suggestion.
Thanks,
Taylor
PATCH v3 1/12 pkt-line: eliminate the need for static buffer in packet_write_gently()
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Teach packet_write_gently() to write the pkt-line header and the actual buffer in 2 separate calls to write_in_full() and avoid the need for a static buffer, thread-safe scratch space, or an excessively large stack buffer.
Change the API of write_packetized_from_fd() to accept a scratch space argument from its caller to avoid similar issues here.
These changes are intended to make it easier to use pkt-line routines in a multi-threaded context with multiple concurrent writers writing to different streams.
--- convert.c | 7 ++++--- pkt-line.c | 28 +++++++++++++++++++--------- pkt-line.h | 12 +++++++++--- 3 files changed, 32 insertions(+), 15 deletions(-)
convert.c +4 -3
@@ -883,9 +883,10 @@ static int apply_multi_file_filter(const char *path, const char *src, size_t lenif (err) goto done; -if (fd >=0 ) - err =write_packetized_from_fd (fd, process->in); -else +if (fd >=0 ) { +struct packet_scratch_space scratch; + err =write_packetized_from_fd (fd, process->in, &scratch); + }else err =write_packetized_from_buf (src, len, process->in);if (err) goto done;
pkt-line.c +19 -9
@@ -196,17 +196,25 @@ int packet_write_fmt_gently(int fd, const char *fmt, ...)static int packet_write_gently (const int fd_out,const char *buf,size_t size) { -static char packet_write_buffer[LARGE_PACKET_MAX ]; +char header[4 ];size_t packet_size; -if (size >sizeof (packet_write_buffer) -4 ) +if (size >LARGE_PACKET_DATA_MAX )return error (_ ("packet write failed - data exceeds max packet size" ));packet_trace (buf, size,1 ); packet_size = size +4 ; -set_packet_header (packet_write_buffer, packet_size); -memcpy (packet_write_buffer +4 , buf, size); -if (write_in_full (fd_out, packet_write_buffer, packet_size) <0 ) + +set_packet_header (header, packet_size); + +/* +* Write the header and the buffer in 2 parts so that we do not need +* to allocate a buffer or rely on a static buffer. This avoids perf +* and multi-threading issues. +*/ + +if (write_in_full (fd_out, header,4 ) <0 || +write_in_full (fd_out, buf, size) <0 )return error (_ ("packet write failed" ));return 0 ; }
@@ -242,19 +250,21 @@ void packet_buf_write_len(struct strbuf *buf, const char *data, size_t len)packet_trace (data, len,1 ); } -int write_packetized_from_fd (int fd_in,int fd_out) +int write_packetized_from_fd (int fd_in,int fd_out, +struct packet_scratch_space *scratch) { -static char buf[LARGE_PACKET_DATA_MAX ];int err =0 ;ssize_t bytes_to_write;while (!err) { - bytes_to_write =xread (fd_in, buf,sizeof (buf)); + bytes_to_write =xread (fd_in, scratch->buffer, +sizeof (scratch->buffer));if (bytes_to_write <0 )return COPY_READ_ERROR ;if (bytes_to_write ==0 )break ; - err =packet_write_gently (fd_out, buf, bytes_to_write); + err =packet_write_gently (fd_out, scratch->buffer, + bytes_to_write); }if (!err) err =packet_flush_gently (fd_out);
pkt-line.h +9 -3
@@ -5,6 +5,13 @@#include "strbuf.h" #include "sideband.h" +#define LARGE_PACKET_MAX 65520 +#define LARGE_PACKET_DATA_MAX (LARGE_PACKET_MAX - 4) + +struct packet_scratch_space { +char buffer[LARGE_PACKET_DATA_MAX ];/* does not include header bytes */ +}; +/* * Write a packetized stream, where each line is preceded by * its length (including the header) as a 4-byte hex number.
@@ -32,7 +39,7 @@ void packet_buf_write(struct strbuf *buf, const char *fmt, ...) __attribute__((fvoid packet_buf_write_len (struct strbuf *buf,const char *data,size_t len);int packet_flush_gently (int fd);int packet_write_fmt_gently (int fd,const char *fmt, ...) __attribute__((format (printf,2 ,3 ))); -int write_packetized_from_fd (int fd_in,int fd_out); +int write_packetized_from_fd (int fd_in,int fd_out,struct packet_scratch_space *scratch);int write_packetized_from_buf (const char *src_in,size_t len,int fd_out);/*
@@ -213,8 +220,7 @@ enum packet_read_status packet_reader_read(struct packet_reader *reader);enum packet_read_status packet_reader_peek (struct packet_reader *reader);#define DEFAULT_PACKET_MAX 1000 -#define LARGE_PACKET_MAX 65520 -#define LARGE_PACKET_DATA_MAX (LARGE_PACKET_MAX - 4) +extern char packet_buffer[LARGE_PACKET_MAX ];struct packet_writer {
Signature · 1 line
gitgitgadget
PATCH v3 2/12 pkt-line: do not issue flush packets in write_packetized_*()
In reply to Jeff Hostetler via GitGitGadgetFrom: Johannes Schindelin <[email protected]>
Remove the packet_flush_gently() call in write_packetized_from_buf() and write_packetized_from_fd() and require the caller to call it if desired. Rename both functions to write_packetized_from_*_no_flush()` to prevent later merge accidents.
write_packetized_from_buf() currently only has one caller:apply_multi_file_filter() in convert.c. It always wants a flush packet to be written after writing the payload.
However, we are about to introduce a caller that wants to write many packets before a final flush packet, so let's make the caller responsible for emitting the flush packet.
--- convert.c | 8 ++++++-- pkt-line.c | 10 +++------- pkt-line.h | 4 ++-- 3 files changed, 11 insertions(+), 11 deletions(-)
convert.c +6 -2
@@ -885,9 +885,13 @@ static int apply_multi_file_filter(const char *path, const char *src, size_t lenif (fd >=0 ) {struct packet_scratch_space scratch; - err =write_packetized_from_fd (fd, process->in, &scratch); + err =write_packetized_from_fd_no_flush (fd, process->in, &scratch); }else - err =write_packetized_from_buf (src, len, process->in); + err =write_packetized_from_buf_no_flush (src, len, process->in); +if (err) + goto done; + + err =packet_flush_gently (process->in);if (err) goto done;
pkt-line.c +3 -7
@@ -250,8 +250,8 @@ void packet_buf_write_len(struct strbuf *buf, const char *data, size_t len)packet_trace (data, len,1 ); } -int write_packetized_from_fd (int fd_in,int fd_out, -struct packet_scratch_space *scratch) +int write_packetized_from_fd_no_flush (int fd_in,int fd_out, +struct packet_scratch_space *scratch) {int err =0 ;ssize_t bytes_to_write;
@@ -266,12 +266,10 @@ int write_packetized_from_fd(int fd_in, int fd_out, err =packet_write_gently (fd_out, scratch->buffer, bytes_to_write); } -if (!err) - err =packet_flush_gently (fd_out);return err; } -int write_packetized_from_buf (const char *src_in,size_t len,int fd_out) +int write_packetized_from_buf_no_flush (const char *src_in,size_t len,int fd_out) {int err =0 ;size_t bytes_written =0 ;
@@ -287,8 +285,6 @@ int write_packetized_from_buf(const char *src_in, size_t len, int fd_out) err =packet_write_gently (fd_out, src_in + bytes_written, bytes_to_write); bytes_written += bytes_to_write; } -if (!err) - err =packet_flush_gently (fd_out);return err; }
pkt-line.h +2 -2
@@ -39,8 +39,8 @@ void packet_buf_write(struct strbuf *buf, const char *fmt, ...) __attribute__((fvoid packet_buf_write_len (struct strbuf *buf,const char *data,size_t len);int packet_flush_gently (int fd);int packet_write_fmt_gently (int fd,const char *fmt, ...) __attribute__((format (printf,2 ,3 ))); -int write_packetized_from_fd (int fd_in,int fd_out,struct packet_scratch_space *scratch); -int write_packetized_from_buf (const char *src_in,size_t len,int fd_out); +int write_packetized_from_fd_no_flush (int fd_in,int fd_out,struct packet_scratch_space *scratch); +int write_packetized_from_buf_no_flush (const char *src_in,size_t len,int fd_out);/* * Read a packetized line into the buffer, which must be at least size bytes
Signature · 1 line
gitgitgadget
PATCH v3 3/12 pkt-line: (optionally) libify the packet readers
In reply to Jeff Hostetler via GitGitGadgetFrom: Johannes Schindelin <[email protected]>
So far, the (possibly indirect) callers of get_packet_data() can ask that function to return an error instead of die()ing upon end-of-file. However, random read errors will still cause the process to die.
So let's introduce an explicit option to tell the packet reader machinery to please be nice and only return an error.
This change prepares pkt-line for use by long-running daemon processes. Such processes should be able to serve multiple concurrent clients and and survive random IO errors. If there is an error on one connection, a daemon should be able to drop that connection and continue serving existing and future connections.
This ability will be used by a Git-aware "Internal FSMonitor" feature in a later patch series.
--- pkt-line.c | 19 +++++++++++++++++-- pkt-line.h | 4 ++++ 2 files changed, 21 insertions(+), 2 deletions(-)
pkt-line.c +17 -2
@@ -304,8 +304,11 @@ static int get_packet_data(int fd, char **src_buf, size_t *src_size, *src_size -= ret; }else { ret =read_in_full (fd, dst, size); -if (ret <0 ) +if (ret <0 ) { +if (options &PACKET_READ_NEVER_DIE ) +return error_errno (_ ("read error" ));die_errno (_ ("read error" )); + } }/* And complain if we didn't get enough bytes to satisfy the read. */
@@ -313,6 +316,8 @@ static int get_packet_data(int fd, char **src_buf, size_t *src_size,if (options &PACKET_READ_GENTLE_ON_EOF )return -1 ; +if (options &PACKET_READ_NEVER_DIE ) +return error (_ ("the remote end hung up unexpectedly" ));die (_ ("the remote end hung up unexpectedly" )); }
@@ -341,6 +346,9 @@ enum packet_read_status packet_read_with_status(int fd, char **src_buffer, len =packet_length (linelen);if (len <0 ) { +if (options &PACKET_READ_NEVER_DIE ) +return error (_ ("protocol error: bad line length " +"character: %.4s" ), linelen);die (_ ("protocol error: bad line length character: %.4s" ), linelen); }else if (!len) {packet_trace ("0000" ,4 ,0 );
@@ -355,12 +363,19 @@ enum packet_read_status packet_read_with_status(int fd, char **src_buffer, *pktlen =0 ;return PACKET_READ_RESPONSE_END ; }else if (len <4 ) { +if (options &PACKET_READ_NEVER_DIE ) +return error (_ ("protocol error: bad line length %d" ), + len);die (_ ("protocol error: bad line length %d" ), len); } len -=4 ; -if ((unsigned )len >= size) +if ((unsigned )len >= size) { +if (options &PACKET_READ_NEVER_DIE ) +return error (_ ("protocol error: bad line length %d" ), + len);die (_ ("protocol error: bad line length %d" ), len); + }if (get_packet_data (fd, src_buffer, src_len, buffer, len, options) <0 ) { *pktlen =-1 ;
pkt-line.h +4 -0
@@ -75,10 +75,14 @@ int write_packetized_from_buf_no_flush(const char *src_in, size_t len, int fd_ou* * If options contains PACKET_READ_DIE_ON_ERR_PACKET, it dies when it sees an * ERR packet. +* +* With `PACKET_READ_NEVER_DIE`, no errors are allowed to trigger die() (except +* an ERR packet, when `PACKET_READ_DIE_ON_ERR_PACKET` is in effect). */ #define PACKET_READ_GENTLE_ON_EOF (1u<<0)#define PACKET_READ_CHOMP_NEWLINE (1u<<1)#define PACKET_READ_DIE_ON_ERR_PACKET (1u<<2) +#define PACKET_READ_NEVER_DIE (1u<<3)int packet_read (int fd,char **src_buffer,size_t *src_len,char *buffer,unsigned size,int options);
Signature · 1 line
gitgitgadget
PATCH v3 4/12 pkt-line: add options argument to read_packetized_to_strbuf()
In reply to Jeff Hostetler via GitGitGadgetFrom: Johannes Schindelin <[email protected]>
Update the calling sequence of read_packetized_to_strbuf() to take an options argument and not assume a fixed set of options. Update the only existing caller accordingly to explicitly pass the formerly-assumed flags.
The read_packetized_to_strbuf() function calls packet_read() with a fixed set of assumed options (PACKET_READ_GENTLE_ON_EOF). This assumption has been fine for the single existing caller apply_multi_file_filter() in convert.c.
In a later commit we would like to add other callers to read_packetized_to_strbuf() that need a different set of options.
--- convert.c | 3 ++- pkt-line.c | 4 ++-- pkt-line.h | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-)
convert.c +2 -1
@@ -908,7 +908,8 @@ static int apply_multi_file_filter(const char *path, const char *src, size_t lenif (err) goto done; - err =read_packetized_to_strbuf (process->out, &nbuf) <0 ; + err =read_packetized_to_strbuf (process->out, &nbuf, +PACKET_READ_GENTLE_ON_EOF ) <0 ;if (err) goto done;
pkt-line.c +2 -2
@@ -442,7 +442,7 @@ char *packet_read_line_buf(char **src, size_t *src_len, int *dst_len)return packet_read_line_generic (-1 , src, src_len, dst_len); } -ssize_t read_packetized_to_strbuf (int fd_in,struct strbuf *sb_out) +ssize_t read_packetized_to_strbuf (int fd_in,struct strbuf *sb_out,int options) {int packet_len;
@@ -458,7 +458,7 @@ ssize_t read_packetized_to_strbuf(int fd_in, struct strbuf *sb_out)* that there is already room for the extra byte. */ sb_out->buf + sb_out->len,LARGE_PACKET_DATA_MAX +1 , - PACKET_READ_GENTLE_ON_EOF); + options);if (packet_len <=0 )break ; sb_out->len += packet_len;
pkt-line.h +1 -1
@@ -142,7 +142,7 @@ char *packet_read_line_buf(char **src_buf, size_t *src_len, int *size);/* * Reads a stream of variable sized packets until a flush packet is detected. */ -ssize_t read_packetized_to_strbuf (int fd_in,struct strbuf *sb_out); +ssize_t read_packetized_to_strbuf (int fd_in,struct strbuf *sb_out,int options);/* * Receive multiplexed output stream over git native protocol.
Signature · 1 line
gitgitgadget
PATCH v3 5/12 simple-ipc: design documentation for new IPC mechanism
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Brief design documentation for new IPC mechanism allowing foreground Git client to talk with an existing daemon process at a known location using a named pipe or unix domain socket.
--- Documentation/technical/api-simple-ipc.txt | 34 ++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 Documentation/technical/api-simple-ipc.txt
Documentation/technical/api-simple-ipc.txt new file +34 -0
@@ -0,0 +1,34 @@ +simple-ipc API +============== + +The simple-ipc API is used to send an IPC message and response between +a (presumably) foreground Git client process to a background server or +daemon process. The server process must already be running. Multiple +client processes can simultaneously communicate with the server +process. + +Communication occurs over a named pipe on Windows and a Unix domain +socket on other platforms. Clients and the server rendezvous at a +previously agreed-to application-specific pathname (which is outside +the scope of this design). + +This IPC mechanism differs from the existing `sub-process.c` model +(Documentation/technical/long-running-process-protocol.txt) and used +by applications like Git-LFS. In the simple-ipc model the server is +assumed to be a very long-running system service. In contrast, in the +LFS-style sub-process model the helper is started with the foreground +process and exits when the foreground process terminates. + +How the simple-ipc server is started is also outside the scope of the +IPC mechanism. For example, the server might be started during +maintenance operations. + +The IPC protocol consists of a single request message from the client and +an optional request message from the server. For simplicity, pkt-line +routines are used to hide chunking and buffering concerns. Each side +terminates their message with a flush packet. +(Documentation/technical/protocol-common.txt) + +The actual format of the client and server messages is application +specific. The IPC layer transmits and receives an opaque buffer without +any concern for the content within.
Signature · 1 line
gitgitgadget
PATCH v3 6/12 simple-ipc: add win32 implementation
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Create Windows implementation of "simple-ipc" using named pipes.
--- Makefile | 5 + compat/simple-ipc/ipc-shared.c | 28 ++ compat/simple-ipc/ipc-win32.c | 749 ++++++++++++++++++++++++++++ config.mak.uname | 2 + contrib/buildsystems/CMakeLists.txt | 4 + simple-ipc.h | 224 +++++++++ 6 files changed, 1012 insertions(+) create mode 100644 compat/simple-ipc/ipc-shared.c create mode 100644 compat/simple-ipc/ipc-win32.c create mode 100644 simple-ipc.h
Makefile +5 -0
@@ -1679,6 +1679,11 @@ else LIB_OBJS += unix-socket.o endif +ifdef USE_WIN32_IPC + LIB_OBJS += compat/simple-ipc/ipc-shared.o + LIB_OBJS += compat/simple-ipc/ipc-win32.o +endif + ifdef NO_ICONV BASIC_CFLAGS += -DNO_ICONV endif
compat/simple-ipc/ipc-shared.c new file +28 -0
@@ -0,0 +1,28 @@ +#include "cache.h" +#include "simple-ipc.h" +#include "strbuf.h" +#include "pkt-line.h" +#include "thread-utils.h" + +#ifdef SUPPORTS_SIMPLE_IPC + +int ipc_server_run (const char *path,const struct ipc_server_opts *opts, +ipc_server_application_cb *application_cb, +void *application_data) +{ +struct ipc_server_data *server_data =NULL ; +int ret; + + ret =ipc_server_run_async (&server_data, path, opts, + application_cb, application_data); +if (ret) +return ret; + + ret =ipc_server_await (server_data); + +ipc_server_free (server_data); + +return ret; +} + +#endif /* SUPPORTS_SIMPLE_IPC */
compat/simple-ipc/ipc-win32.c new file +749 -0
749 lines not on this page · raw message
config.mak.uname +2 -0
@@ -421,6 +421,7 @@ ifeq ($(uname_S),Windows) RUNTIME_PREFIX = YesPlease HAVE_WPGMPTR = YesWeDo NO_ST_BLOCKS_IN_STRUCT_STAT = YesPlease + USE_WIN32_IPC = YesPlease USE_WIN32_MMAP = YesPlease MMAP_PREVENTS_DELETE = UnfortunatelyYes # USE_NED_ALLOCATOR = YesPlease
@@ -597,6 +598,7 @@ ifneq (,$(findstring MINGW,$(uname_S))) RUNTIME_PREFIX = YesPlease HAVE_WPGMPTR = YesWeDo NO_ST_BLOCKS_IN_STRUCT_STAT = YesPlease + USE_WIN32_IPC = YesPlease USE_WIN32_MMAP = YesPlease MMAP_PREVENTS_DELETE = UnfortunatelyYes USE_NED_ALLOCATOR = YesPlease
contrib/buildsystems/CMakeLists.txt +4 -0
@@ -246,6 +246,10 @@ elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux") list(APPEND compat_SOURCES unix-socket.c) endif() +if(CMAKE_SYSTEM_NAME STREQUAL "Windows") + list(APPEND compat_SOURCES compat/simple-ipc/ipc-shared.c compat/simple-ipc/ipc-win32.c) +endif() + set(EXE_EXTENSION ${CMAKE_EXECUTABLE_SUFFIX}) #header checks
simple-ipc.h new file +224 -0
224 lines not on this page · raw message
Signature · 1 line
gitgitgadget
v3 cover Simple IPC Mechanism
In reply to Jeff Hostetler via GitGitGadgetHere is version 3 of my "Simple IPC" series. It addresses the following review comments from V2:
[1] Convert packet_write_gently() to write the header length and then the actual buffer using 2 syscalls and avoid the need for a static or stack buffer and update callers.
[2] Added buffer argument to write_packetized_from_fd() to force (the one caller) to provide a buffer and avoid the same thread issues discussed earlier.
[3] Remove the implicit pkt-flush from write_packetized_from_buf(). (V2 added a flag to make it optional and I removed that too.) Updated the existing callers to call packet_flush_gently() as desired. Renamed write_packetized_*() functions to have ..._no_flush() suffix to prevent future accidents with new (more limited) functionality.
[4] Removed the "force_unlink" flag to the unix-socket options that I added in V1/V2.
[5] Created a new unix_stream_server__listen_with_lock() wrapper function to safely create a Unix domain socket while holding a lockfile and (hopefully) eliminate the previously discussed race conditions. Added a little helper struct and related routines to help manage the life of the socket.
[6] Added test-tool simple-ipc start-daemon to launch a background instance of test-tool simple-ipc run-daemon and wait for the server to become ready before exiting. And updated t0052 to use it and avoid the problematic sleep 1 in V1/V2. (There was discussion on the mailing list about using a FIFO in the test like lib-git-daemon.sh, but there are issues with FIFO support on Windows that I didn't want to step into. (And I want to use the same "run" and "start" technique with the FSMonitor layer, so lets me explore that here.)
[7] Rebased onto v2.30.1 to get rid of a copy of Junio's "brew cask" commit (3831132ace) that I included in earlier versions of this series.
[8] In response Gábor's comments about a CI test failure on "quit works"
(https://lore.kernel.org/git/[email protected]/) I added a generous sleep and comments. I'm not completely happy with this solution, but I'm not sure of a better solution right now.
[9] In response to Taylor's comment on read_packetized_to_strbuf()
(https://lore.kernel.org/git/[email protected]/), I've moved PACKET_READ_GENTLE_ON_EOF flag to all the callers as suggested.
cc: Ævar Arnfjörð Bjarmason [email protected] cc: Jeff Hostetler [email protected] cc: Jeff King [email protected] cc: Chris Torek [email protected]
Jeff Hostetler (9):
pkt-line: eliminate the need for static buffer in
packet_write_gently()
simple-ipc: design documentation for new IPC mechanism
simple-ipc: add win32 implementation
unix-socket: elimiate static unix_stream_socket() helper function
unix-socket: add backlog size option to unix_stream_listen()
unix-socket: disallow chdir() when creating unix domain sockets
unix-socket: create unix_stream_server__listen_with_lock()
simple-ipc: add Unix domain socket implementation
t0052: add simple-ipc tests and t/helper/test-simple-ipc tool
Johannes Schindelin (3):
pkt-line: do not issue flush packets in write_packetized_*()
pkt-line: (optionally) libify the packet readers
pkt-line: add options argument to read_packetized_to_strbuf()
Documentation/technical/api-simple-ipc.txt | 34 + Makefile | 8 + builtin/credential-cache--daemon.c | 3 +- builtin/credential-cache.c | 2 +- compat/simple-ipc/ipc-shared.c | 28 + compat/simple-ipc/ipc-unix-socket.c | 979 +++++++++++++++++++++ compat/simple-ipc/ipc-win32.c | 749 ++++++++++++++++ config.mak.uname | 2 + contrib/buildsystems/CMakeLists.txt | 6 + convert.c | 16 +- pkt-line.c | 57 +- pkt-line.h | 20 +- simple-ipc.h | 235 +++++ t/helper/test-simple-ipc.c | 713 +++++++++++++++ t/helper/test-tool.c | 1 + t/helper/test-tool.h | 1 + t/t0052-simple-ipc.sh | 134 +++ unix-socket.c | 168 +++- unix-socket.h | 47 +- 19 files changed, 3150 insertions(+), 53 deletions(-) create mode 100644 Documentation/technical/api-simple-ipc.txt create mode 100644 compat/simple-ipc/ipc-shared.c create mode 100644 compat/simple-ipc/ipc-unix-socket.c create mode 100644 compat/simple-ipc/ipc-win32.c create mode 100644 simple-ipc.h create mode 100644 t/helper/test-simple-ipc.c create mode 100755 t/t0052-simple-ipc.sh
base-commit: 773e25afc41b1b6533fa9ae2cd825d0b4a697fad
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-766%2Fjeffhostetler%2Fsimple-ipc-v3
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-766/jeffhostetler/simple-ipc-v3
Pull-Request: https://github.com/gitgitgadget/git/pull/766
Range-diff vs v2:
1: 4c6766d41834 < -: ------------ ci/install-depends: attempt to fix "brew cask" stuff
2: 3b03a8ff7a72 ! 1: 2d6858b1625a pkt-line: promote static buffer in packet_write_gently() to callers
@@ Metadata
Author: Jeff Hostetler <[email protected]>
## Commit message ##
- pkt-line: promote static buffer in packet_write_gently() to callers
+ pkt-line: eliminate the need for static buffer in packet_write_gently()
- Move the static buffer used in `packet_write_gently()` to its callers.
- This is a first step to make packet writing more thread-safe.
+ Teach `packet_write_gently()` to write the pkt-line header and the actual
+ buffer in 2 separate calls to `write_in_full()` and avoid the need for a
+ static buffer, thread-safe scratch space, or an excessively large stack
+ buffer.
+
+ Change the API of `write_packetized_from_fd()` to accept a scratch space
+ argument from its caller to avoid similar issues here.
+
+ These changes are intended to make it easier to use pkt-line routines in
+ a multi-threaded context with multiple concurrent writers writing to
+ different streams.
Signed-off-by: Jeff Hostetler <[email protected]>
+ ## convert.c ##
+@@ convert.c: static int apply_multi_file_filter(const char *path, const char *src, size_t len
+ if (err)
+ goto done;
+
+- if (fd >= 0)
+- err = write_packetized_from_fd(fd, process->in);
+- else
++ if (fd >= 0) {
++ struct packet_scratch_space scratch;
++ err = write_packetized_from_fd(fd, process->in, &scratch);
++ } else
+ err = write_packetized_from_buf(src, len, process->in);
+ if (err)
+ goto done;
+
## pkt-line.c ##
@@ pkt-line.c: int packet_write_fmt_gently(int fd, const char *fmt, ...)
- return status;
- }
--static int packet_write_gently(const int fd_out, const char *buf, size_t size)
-+/*
-+ * Use the provided scratch space to build a combined <hdr><buf> buffer
-+ * and write it to the file descriptor (in one write if possible).
-+ */
-+static int packet_write_gently(const int fd_out, const char *buf, size_t size,
-+ struct packet_scratch_space *scratch)
+ static int packet_write_gently(const int fd_out, const char *buf, size_t size)
{
- static char packet_write_buffer[LARGE_PACKET_MAX];
++ char header[4];
size_t packet_size;
- if (size > sizeof(packet_write_buffer) - 4)
-+ if (size > sizeof(scratch->buffer) - 4)
++ if (size > LARGE_PACKET_DATA_MAX)
return error(_("packet write failed - data exceeds max packet size"));
packet_trace(buf, size, 1);
@@ pkt-line.c: int packet_write_fmt_gently(int fd, const char *fmt, ...)
- memcpy(packet_write_buffer + 4, buf, size);
- if (write_in_full(fd_out, packet_write_buffer, packet_size) < 0)
+
-+ set_packet_header(scratch->buffer, packet_size);
-+ memcpy(scratch->buffer + 4, buf, size);
++ set_packet_header(header, packet_size);
+
-+ if (write_in_full(fd_out, scratch->buffer, packet_size) < 0)
++ /*
++ * Write the header and the buffer in 2 parts so that we do not need
++ * to allocate a buffer or rely on a static buffer. This avoids perf
++ * and multi-threading issues.
++ */
++
++ if (write_in_full(fd_out, header, 4) < 0 ||
++ write_in_full(fd_out, buf, size) < 0)
return error(_("packet write failed"));
return 0;
}
-
- void packet_write(int fd_out, const char *buf, size_t size)
- {
-- if (packet_write_gently(fd_out, buf, size))
-+ static struct packet_scratch_space scratch;
-+
-+ if (packet_write_gently(fd_out, buf, size, &scratch))
- die_errno(_("packet write failed"));
- }
-
@@ pkt-line.c: void packet_buf_write_len(struct strbuf *buf, const char *data, size_t len)
+ packet_trace(data, len, 1);
+ }
- int write_packetized_from_fd(int fd_in, int fd_out)
+-int write_packetized_from_fd(int fd_in, int fd_out)
++int write_packetized_from_fd(int fd_in, int fd_out,
++ struct packet_scratch_space *scratch)
{
-+ /*
-+ * TODO We could save a memcpy() if we essentially inline
-+ * TODO packet_write_gently() here and change the xread()
-+ * TODO to pass &buf[4].
-+ */
-+ static struct packet_scratch_space scratch;
- static char buf[LARGE_PACKET_DATA_MAX];
+- static char buf[LARGE_PACKET_DATA_MAX];
int err = 0;
ssize_t bytes_to_write;
-@@ pkt-line.c: int write_packetized_from_fd(int fd_in, int fd_out)
+
+ while (!err) {
+- bytes_to_write = xread(fd_in, buf, sizeof(buf));
++ bytes_to_write = xread(fd_in, scratch->buffer,
++ sizeof(scratch->buffer));
+ if (bytes_to_write < 0)
return COPY_READ_ERROR;
if (bytes_to_write == 0)
break;
- err = packet_write_gently(fd_out, buf, bytes_to_write);
-+ err = packet_write_gently(fd_out, buf, bytes_to_write, &scratch);
++ err = packet_write_gently(fd_out, scratch->buffer,
++ bytes_to_write);
}
if (!err)
err = packet_flush_gently(fd_out);
-@@ pkt-line.c: int write_packetized_from_fd(int fd_in, int fd_out)
-
- int write_packetized_from_buf(const char *src_in, size_t len, int fd_out)
- {
-+ static struct packet_scratch_space scratch;
- int err = 0;
- size_t bytes_written = 0;
- size_t bytes_to_write;
-@@ pkt-line.c: int write_packetized_from_buf(const char *src_in, size_t len, int fd_out)
- bytes_to_write = len - bytes_written;
- if (bytes_to_write == 0)
- break;
-- err = packet_write_gently(fd_out, src_in + bytes_written, bytes_to_write);
-+ err = packet_write_gently(fd_out, src_in + bytes_written, bytes_to_write, &scratch);
- bytes_written += bytes_to_write;
- }
- if (!err)
## pkt-line.h ##
@@
@@ pkt-line.h
+#define LARGE_PACKET_DATA_MAX (LARGE_PACKET_MAX - 4)
+
+struct packet_scratch_space {
-+ char buffer[LARGE_PACKET_MAX];
++ char buffer[LARGE_PACKET_DATA_MAX]; /* does not include header bytes */
+};
+
/*
* Write a packetized stream, where each line is preceded by
* its length (including the header) as a 4-byte hex number.
+@@ pkt-line.h: void packet_buf_write(struct strbuf *buf, const char *fmt, ...) __attribute__((f
+ void packet_buf_write_len(struct strbuf *buf, const char *data, size_t len);
+ int packet_flush_gently(int fd);
+ int packet_write_fmt_gently(int fd, const char *fmt, ...) __attribute__((format (printf, 2, 3)));
+-int write_packetized_from_fd(int fd_in, int fd_out);
++int write_packetized_from_fd(int fd_in, int fd_out, struct packet_scratch_space *scratch);
+ int write_packetized_from_buf(const char *src_in, size_t len, int fd_out);
+
+ /*
@@ pkt-line.h: enum packet_read_status packet_reader_read(struct packet_reader *reader);
enum packet_read_status packet_reader_peek(struct packet_reader *reader);3: e671894b4c04 < -: ------------ pkt-line: add write_packetized_from_buf2() that takes scratch buffer 4: 0832f7d324da ! 2: 91a9f63d6692 pkt-line: optionally skip the flush packet in write_packetized_from_buf()
@@ Metadata
Author: Johannes Schindelin <[email protected]>
## Commit message ##
- pkt-line: optionally skip the flush packet in write_packetized_from_buf()
+ pkt-line: do not issue flush packets in write_packetized_*()
- This function currently has only one caller: apply_multi_file_filter()
- in convert.c. That caller wants a flush packet to be written after
- writing the payload.
+ Remove the packet_flush_gently() call in `write_packetized_from_buf() and
+ write_packetized_from_fd() and require the caller to call it if desired.
+ Rename both functions to write_packetized_from_*_no_flush() to prevent
+ later merge accidents.
- However, we are about to introduce a user that wants to write many
- packets before a final flush packet, so let's extend this function to
- prepare for that scenario.
+ write_packetized_from_buf() currently only has one caller:
+ apply_multi_file_filter() in convert.c. It always wants a flush packet
+ to be written after writing the payload.
+
+ However, we are about to introduce a caller that wants to write many
+ packets before a final flush packet, so let's make the caller responsible
+ for emitting the flush packet.
## convert.c ##
@@ convert.c: static int apply_multi_file_filter(const char *path, const char *src, size_t len
- if (fd >= 0)
- err = write_packetized_from_fd(fd, process->in);
- else
+
+ if (fd >= 0) {
+ struct packet_scratch_space scratch;
+- err = write_packetized_from_fd(fd, process->in, &scratch);
++ err = write_packetized_from_fd_no_flush(fd, process->in, &scratch);
+ } else
- err = write_packetized_from_buf(src, len, process->in);
-+ err = write_packetized_from_buf(src, len, process->in, 1);
++ err = write_packetized_from_buf_no_flush(src, len, process->in);
++ if (err)
++ goto done;
++
++ err = packet_flush_gently(process->in);
if (err)
goto done;
## pkt-line.c ##
-@@ pkt-line.c: int write_packetized_from_fd(int fd_in, int fd_out)
- return err;
+@@ pkt-line.c: void packet_buf_write_len(struct strbuf *buf, const char *data, size_t len)
+ packet_trace(data, len, 1);
}
--int write_packetized_from_buf(const char *src_in, size_t len, int fd_out) -+int write_packetized_from_buf(const char *src_in, size_t len, int fd_out,
-+ int flush_at_end)
+-int write_packetized_from_fd(int fd_in, int fd_out,
+- struct packet_scratch_space *scratch)
++int write_packetized_from_fd_no_flush(int fd_in, int fd_out,
++ struct packet_scratch_space *scratch)
{
- static struct packet_scratch_space scratch;
-
-- return write_packetized_from_buf2(src_in, len, fd_out, &scratch);
-+ return write_packetized_from_buf2(src_in, len, fd_out,
-+ flush_at_end, &scratch);
+ int err = 0;
+ ssize_t bytes_to_write;
+@@ pkt-line.c: int write_packetized_from_fd(int fd_in, int fd_out,
+ err = packet_write_gently(fd_out, scratch->buffer,
+ bytes_to_write);
+ }
+- if (!err)
+- err = packet_flush_gently(fd_out);
+ return err;
}
- int write_packetized_from_buf2(const char *src_in, size_t len, int fd_out,
-+ int flush_at_end,
- struct packet_scratch_space *scratch)
+-int write_packetized_from_buf(const char *src_in, size_t len, int fd_out) ++int write_packetized_from_buf_no_flush(const char *src_in, size_t len, int fd_out)
{
int err = 0;
-@@ pkt-line.c: int write_packetized_from_buf2(const char *src_in, size_t len, int fd_out,
- err = packet_write_gently(fd_out, src_in + bytes_written, bytes_to_write, scratch);
+ size_t bytes_written = 0;
+@@ pkt-line.c: int write_packetized_from_buf(const char *src_in, size_t len, int fd_out)
+ err = packet_write_gently(fd_out, src_in + bytes_written, bytes_to_write);
bytes_written += bytes_to_write;
}
- if (!err)
-+ if (!err && flush_at_end)
- err = packet_flush_gently(fd_out);
+- err = packet_flush_gently(fd_out);
return err;
}
+
## pkt-line.h ##
-@@ pkt-line.h: void packet_buf_write_len(struct strbuf *buf, const char *data, size_t len); +@@ pkt-line.h: void packet_buf_write(struct strbuf *buf, const char *fmt, ...) __attribute__((f
+ void packet_buf_write_len(struct strbuf *buf, const char *data, size_t len);
int packet_flush_gently(int fd);
int packet_write_fmt_gently(int fd, const char *fmt, ...) __attribute__((format (printf, 2, 3)));
- int write_packetized_from_fd(int fd_in, int fd_out);
+-int write_packetized_from_fd(int fd_in, int fd_out, struct packet_scratch_space *scratch);
-int write_packetized_from_buf(const char *src_in, size_t len, int fd_out);
-+int write_packetized_from_buf(const char *src_in, size_t len, int fd_out,
-+ int flush_at_end);
- int write_packetized_from_buf2(const char *src_in, size_t len, int fd_out,
-+ int flush_at_end,
- struct packet_scratch_space *scratch);
++int write_packetized_from_fd_no_flush(int fd_in, int fd_out, struct packet_scratch_space *scratch); ++int write_packetized_from_buf_no_flush(const char *src_in, size_t len, int fd_out);
/*
+ * Read a packetized line into the buffer, which must be at least size bytes
5: 43bc4a26b790 ! 3: e05467def4e1 pkt-line: (optionally) libify the packet readers
@@ pkt-line.c: enum packet_read_status packet_read_with_status(int fd, char **src_b
*pktlen = -1;
## pkt-line.h ##
-@@ pkt-line.h: int write_packetized_from_buf2(const char *src_in, size_t len, int fd_out, +@@ pkt-line.h: int write_packetized_from_buf_no_flush(const char *src_in, size_t len, int fd_ou
*
* If options contains PACKET_READ_DIE_ON_ERR_PACKET, it dies when it sees an
* ERR packet.
6: 6a389a353351 ! 4: 81e14bed955c pkt-line: accept additional options in read_packetized_to_strbuf()
@@ Metadata
Author: Johannes Schindelin <[email protected]>
## Commit message ##
- pkt-line: accept additional options in read_packetized_to_strbuf()
+ pkt-line: add options argument to read_packetized_to_strbuf()
- The read_packetized_to_strbuf() function reads packets into a strbuf
- until a flush packet has been received. So far, it has only one caller:
- apply_multi_file_filter() in convert.c. This caller really only
- needs the PACKET_READ_GENTLE_ON_EOF option to be passed to
- packet_read() (which makes sense in the scenario where packets should
- be read until a flush packet is received).
+ Update the calling sequence of read_packetized_to_strbuf() to take
+ an options argument and not assume a fixed set of options. Update the
+ only existing caller accordingly to explicitly pass the
+ formerly-assumed flags.
- We are about to introduce a caller that wants to pass other options
- through to packet_read(), so let's extend the function signature
- accordingly.
+ The read_packetized_to_strbuf() function calls packet_read() with
+ a fixed set of assumed options (PACKET_READ_GENTLE_ON_EOF). This
+ assumption has been fine for the single existing caller
+ apply_multi_file_filter() in convert.c.
+
+ In a later commit we would like to add other callers to
+ read_packetized_to_strbuf() that need a different set of options.
+ Signed-off-by: Jeff Hostetler <[email protected]>
## convert.c ##
@@ convert.c: static int apply_multi_file_filter(const char *path, const char *src, size_t len
@@ convert.c: static int apply_multi_file_filter(const char *path, const char *src,
goto done;
- err = read_packetized_to_strbuf(process->out, &nbuf) < 0;
-+ err = read_packetized_to_strbuf(process->out, &nbuf, 0) < 0;
++ err = read_packetized_to_strbuf(process->out, &nbuf,
++ PACKET_READ_GENTLE_ON_EOF) < 0;
if (err)
goto done;
@@ pkt-line.c: ssize_t read_packetized_to_strbuf(int fd_in, struct strbuf *sb_out)
*/
sb_out->buf + sb_out->len, LARGE_PACKET_DATA_MAX+1,
- PACKET_READ_GENTLE_ON_EOF);
-+ options | PACKET_READ_GENTLE_ON_EOF);
++ options);
if (packet_len <= 0)
break;
sb_out->len += packet_len;
## pkt-line.h ##
@@ pkt-line.h: char *packet_read_line_buf(char **src_buf, size_t *src_len, int *size);
-
/*
* Reads a stream of variable sized packets until a flush packet is detected.
-+ *
-+ * The options are augmented by PACKET_READ_GENTLE_ON_EOF and passed to -+ * packet_read.
*/
-ssize_t read_packetized_to_strbuf(int fd_in, struct strbuf *sb_out);
-+ssize_t read_packetized_to_strbuf(int fd_in, struct strbuf *sb_out,
-+ int options);
++ssize_t read_packetized_to_strbuf(int fd_in, struct strbuf *sb_out, int options);
/*
* Receive multiplexed output stream over git native protocol.
7: a7275b4bdc2a = 5: 22eec60761a8 simple-ipc: design documentation for new IPC mechanism
8: 388366913d41 ! 6: 171ec43ecfa4 simple-ipc: add win32 implementation
@@ compat/simple-ipc/ipc-win32.c (new)
+
+ trace2_region_enter("ipc-client", "send-command", NULL);
+
-+ if (write_packetized_from_buf2(message, strlen(message),
-+ connection->fd, 1,
-+ &connection->scratch_write_buffer) < 0) {
++ if (write_packetized_from_buf_no_flush(message, strlen(message),
++ connection->fd) < 0 ||
++ packet_flush_gently(connection->fd) < 0) {
+ ret = error(_("could not send IPC command"));
+ goto done;
+ }
+
+ FlushFileBuffers((HANDLE)_get_osfhandle(connection->fd));
+
-+ if (read_packetized_to_strbuf(connection->fd, answer,
-+ PACKET_READ_NEVER_DIE) < 0) {
++ if (read_packetized_to_strbuf(
++ connection->fd, answer,
++ PACKET_READ_GENTLE_ON_EOF | PACKET_READ_NEVER_DIE) < 0) {
+ ret = error(_("could not read IPC response"));
+ goto done;
+ }
@@ compat/simple-ipc/ipc-win32.c (new)
+ struct ipc_server_data *server_data;
+ pthread_t pthread_id;
+ HANDLE hPipe;
-+ struct packet_scratch_space scratch_write_buffer;
+};
+
+/*
@@ compat/simple-ipc/ipc-win32.c (new)
+static int do_io_reply_callback(struct ipc_server_reply_data *reply_data,
+ const char *response, size_t response_len)
+{
-+ struct packet_scratch_space *scratch =
-+ &reply_data->server_thread_data->scratch_write_buffer;
-+
+ if (reply_data->magic != MAGIC_SERVER_REPLY_DATA)
+ BUG("reply_cb called with wrong instance data");
+
-+ return write_packetized_from_buf2(response, response_len,
-+ reply_data->fd, 0, scratch);
++ return write_packetized_from_buf_no_flush(response, response_len,
++ reply_data->fd);
+}
+
+/*
@@ compat/simple-ipc/ipc-win32.c (new)
+ return error(_("could not create fd from pipe for '%s'"),
+ server_thread_data->server_data->buf_path.buf);
+
-+ ret = read_packetized_to_strbuf(reply_data.fd, &buf,
-+ PACKET_READ_NEVER_DIE);
++ ret = read_packetized_to_strbuf(
++ reply_data.fd, &buf,
++ PACKET_READ_GENTLE_ON_EOF | PACKET_READ_NEVER_DIE);
+ if (ret >= 0) {
+ ret = server_thread_data->server_data->application_cb(
+ server_thread_data->server_data->application_data,
@@ simple-ipc.h (new)
+
+struct ipc_client_connection {
+ int fd;
-+ struct packet_scratch_space scratch_write_buffer;
+};
+
+/* 10: f5d5445cf42e ! 7: b368318e6a23 unix-socket: elimiate static unix_stream_socket() helper function
@@ Metadata
## Commit message ##
unix-socket: elimiate static unix_stream_socket() helper function
- The static helper function unix_stream_socket() calls die(). This is not
- appropriate for all callers. Eliminate the wrapper function and move the
- existing error handling to the callers in preparation for adapting specific
- callers.
+ The static helper function unix_stream_socket() calls die(). This
+ is not appropriate for all callers. Eliminate the wrapper function
+ and make the callers propagate the error.
@@ unix-socket.c
static int chdir_len(const char *orig, int len)
{
char *path = xmemdupz(orig, len);
-@@ unix-socket.c: int unix_stream_connect(const char *path)
+@@ unix-socket.c: static int unix_sockaddr_init(struct sockaddr_un *sa, const char *path, +
+ int unix_stream_connect(const char *path)
+ {
+- int fd, saved_errno;
++ int fd = -1, saved_errno;
+ struct sockaddr_un sa;
+ struct unix_sockaddr_context ctx;
if (unix_sockaddr_init(&sa, path, &ctx) < 0)
return -1;
- fd = unix_stream_socket();
+ fd = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (fd < 0)
-+ die_errno("unable to create socket");
++ goto fail;
+
if (connect(fd, (struct sockaddr *)&sa, sizeof(sa)) < 0)
goto fail;
unix_sockaddr_cleanup(&ctx);
+@@ unix-socket.c: int unix_stream_connect(const char *path) +
+ fail:
+ saved_errno = errno;
++ if (fd != -1)
++ close(fd);
+ unix_sockaddr_cleanup(&ctx);
+- close(fd);
+ errno = saved_errno;
+ return -1;
+ }
+
+ int unix_stream_listen(const char *path)
+ {
+- int fd, saved_errno;
++ int fd = -1, saved_errno;
+ struct sockaddr_un sa;
+ struct unix_sockaddr_context ctx;
+
@@ unix-socket.c: int unix_stream_listen(const char *path)
if (unix_sockaddr_init(&sa, path, &ctx) < 0)
@@ unix-socket.c: int unix_stream_listen(const char *path)
- fd = unix_stream_socket();
+ fd = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (fd < 0)
-+ die_errno("unable to create socket");
++ goto fail;
if (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) < 0)
goto fail;
+@@ unix-socket.c: int unix_stream_listen(const char *path)
+
+ fail:
+ saved_errno = errno;
++ if (fd != -1)
++ close(fd);
+ unix_sockaddr_cleanup(&ctx);
+- close(fd);
+ errno = saved_errno;
+ return -1;
+ }
11: 7a6a69dfc20c ! 8: 985b2e02b2df unix-socket: add options to unix_stream_listen()
@@ Metadata
Author: Jeff Hostetler <[email protected]>
## Commit message ##
- unix-socket: add options to unix_stream_listen()
+ unix-socket: add backlog size option to unix_stream_listen()
Update `unix_stream_listen()` to take an options structure to override
- default behaviors. This includes the size of the `listen()` backlog
- and whether it should always unlink the socket file before trying to
- create a new one. Also eliminate calls to `die()` if it cannot create
- a socket.
-
- Normally, `unix_stream_listen()` always tries to `unlink()` the
- socket-path before calling `bind()`. If there is an existing
- server/daemon already bound and listening on that socket-path, our
- `unlink()` would have the effect of disassociating the existing
- server's bound-socket-fd from the socket-path without notifying the
- existing server. The existing server could continue to service
- existing connections (accepted-socket-fd's), but would not receive any
- futher new connections (since clients rendezvous via the socket-path).
- The existing server would effectively be offline but yet appear to be
- active.
-
- Furthermore, `unix_stream_listen()` creates an opportunity for a brief
- race condition for connecting clients if they try to connect in the
- interval between the forced `unlink()` and the subsequent `bind()` (which
- recreates the socket-path that is bound to a new socket-fd in the current
- process).
+ default behaviors. This commit includes the size of the `listen()` backlog.
Signed-off-by: Jeff Hostetler <[email protected]>
@@ unix-socket.c: int unix_stream_connect(const char *path)
+int unix_stream_listen(const char *path,
+ const struct unix_stream_listen_opts *opts)
{
-- int fd, saved_errno;
-+ int fd = -1;
-+ int saved_errno;
-+ int bind_successful = 0;
+ int fd = -1, saved_errno;
+ int backlog;
struct sockaddr_un sa;
struct unix_sockaddr_context ctx;
-- unlink(path);
--
- if (unix_sockaddr_init(&sa, path, &ctx) < 0)
- return -1;
-+
- fd = socket(AF_UNIX, SOCK_STREAM, 0);
- if (fd < 0)
-- die_errno("unable to create socket");
-+ goto fail;
-+
-+ if (opts->force_unlink_before_bind)
-+ unlink(path);
-
+@@ unix-socket.c: int unix_stream_listen(const char *path)
if (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) < 0)
goto fail;
-+ bind_successful = 1;
- if (listen(fd, 5) < 0)
-+ if (opts->listen_backlog_size > 0)
-+ backlog = opts->listen_backlog_size;
-+ else
-+ backlog = 5;
++ backlog = opts->listen_backlog_size;
++ if (backlog <= 0)
++ backlog = DEFAULT_UNIX_STREAM_LISTEN_BACKLOG;
+ if (listen(fd, backlog) < 0)
goto fail;
unix_sockaddr_cleanup(&ctx);
-@@ unix-socket.c: int unix_stream_listen(const char *path)
- fail:
- saved_errno = errno;
- unix_sockaddr_cleanup(&ctx);
-- close(fd);
-+ if (fd != -1)
-+ close(fd);
-+ if (bind_successful)
-+ unlink(path);
- errno = saved_errno;
- return -1;
- }
## unix-socket.h ##
@@
@@ unix-socket.h
+struct unix_stream_listen_opts {
+ int listen_backlog_size;
-+ unsigned int force_unlink_before_bind:1;
+};
+
++#define DEFAULT_UNIX_STREAM_LISTEN_BACKLOG (5)
++
+#define UNIX_STREAM_LISTEN_OPTS_INIT \
+{ \
-+ .listen_backlog_size = 5, \
-+ .force_unlink_before_bind = 1, \
++ .listen_backlog_size = DEFAULT_UNIX_STREAM_LISTEN_BACKLOG, \
+}
+
int unix_stream_connect(const char *path); 12: 745b6d5fb746 ! 9: 1bfa36409d07 unix-socket: add no-chdir option to unix_stream_listen()
@@ Metadata
Author: Jeff Hostetler <[email protected]>
## Commit message ##
- unix-socket: add no-chdir option to unix_stream_listen()
+ unix-socket: disallow chdir() when creating unix domain sockets
Calls to chdir() are dangerous in a multi-threaded context. If
- unix_stream_listen() is given a socket pathname that is too big to
- fit in a sockaddr_un structure, it will chdir() to the parent
- directory of the requested socket pathname, create the socket using a
- relative pathname, and then chdir() back. This is not thread-safe.
+ unix_stream_listen() or unix_stream_connect() is given a socket
+ pathname that is too long to fit in a sockaddr_un structure, it will
+ chdir() to the parent directory of the requested socket pathname,
+ create the socket using a relative pathname, and then chdir() back.
+ This is not thread-safe.
- Add disallow_chdir flag to struct unix_sockaddr_context and change
- all callers to pass an initialized context structure.
-
- Teach unix_sockaddr_init() to not allow calls to chdir() when flag
- is set.
+ Teach unix_sockaddr_init() to not allow calls to chdir() when this
+ flag is set.
- ## unix-socket.c ##
-@@ unix-socket.c: static int chdir_len(const char *orig, int len)
+ ## builtin/credential-cache.c ##
+@@
+ static int send_request(const char *socket, const struct strbuf *out)
+ {
+ int got_data = 0;
+- int fd = unix_stream_connect(socket);
++ int fd = unix_stream_connect(socket, 0);
- struct unix_sockaddr_context {
- char *orig_dir;
-+ unsigned int disallow_chdir:1;
- };
+ if (fd < 0)
+ return -1;
+
+ ## unix-socket.c ##
+@@ unix-socket.c: static void unix_sockaddr_cleanup(struct unix_sockaddr_context *ctx)
+ }
-+#define UNIX_SOCKADDR_CONTEXT_INIT \
-+{ \
-+ .orig_dir=NULL, \
-+ .disallow_chdir=0, \
-+}
-+
- static void unix_sockaddr_cleanup(struct unix_sockaddr_context *ctx)
- {
- if (!ctx->orig_dir)
-@@ unix-socket.c: static int unix_sockaddr_init(struct sockaddr_un *sa, const char *path,
+ static int unix_sockaddr_init(struct sockaddr_un *sa, const char *path,
+- struct unix_sockaddr_context *ctx)
++ struct unix_sockaddr_context *ctx,
++ int disallow_chdir)
{
int size = strlen(path) + 1;
-- ctx->orig_dir = NULL;
-+ if (ctx->disallow_chdir && size > sizeof(sa->sun_path)) {
-+ errno = ENAMETOOLONG;
-+ return -1;
-+ }
-+
+ ctx->orig_dir = NULL;
if (size > sizeof(sa->sun_path)) {
- const char *slash = find_last_dir_sep(path);
+- const char *slash = find_last_dir_sep(path);
++ const char *slash;
const char *dir;
-@@ unix-socket.c: int unix_stream_connect(const char *path)
+ struct strbuf cwd = STRBUF_INIT;
+
++ if (disallow_chdir) {
++ errno = ENAMETOOLONG;
++ return -1;
++ }
++
++ slash = find_last_dir_sep(path);
+ if (!slash) {
+ errno = ENAMETOOLONG;
+ return -1;
+@@ unix-socket.c: static int unix_sockaddr_init(struct sockaddr_un *sa, const char *path,
+ return 0;
+ }
+
+-int unix_stream_connect(const char *path)
++int unix_stream_connect(const char *path, int disallow_chdir)
{
- int fd, saved_errno;
+ int fd = -1, saved_errno;
struct sockaddr_un sa;
-- struct unix_sockaddr_context ctx;
-+ struct unix_sockaddr_context ctx = UNIX_SOCKADDR_CONTEXT_INIT;
+ struct unix_sockaddr_context ctx;
- if (unix_sockaddr_init(&sa, path, &ctx) < 0)
+- if (unix_sockaddr_init(&sa, path, &ctx) < 0)
++ if (unix_sockaddr_init(&sa, path, &ctx, disallow_chdir) < 0)
return -1;
+ fd = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (fd < 0)
@@ unix-socket.c: int unix_stream_listen(const char *path,
- int bind_successful = 0;
- int backlog;
- struct sockaddr_un sa;
-- struct unix_sockaddr_context ctx;
-+ struct unix_sockaddr_context ctx = UNIX_SOCKADDR_CONTEXT_INIT;
-+
-+ ctx.disallow_chdir = opts->disallow_chdir;
- if (unix_sockaddr_init(&sa, path, &ctx) < 0)
+ unlink(path);
+
+- if (unix_sockaddr_init(&sa, path, &ctx) < 0)
++ if (unix_sockaddr_init(&sa, path, &ctx, opts->disallow_chdir) < 0)
return -1;
+ fd = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (fd < 0)
## unix-socket.h ##
@@
+
struct unix_stream_listen_opts {
int listen_backlog_size;
- unsigned int force_unlink_before_bind:1;
+ unsigned int disallow_chdir:1;
};
+ #define DEFAULT_UNIX_STREAM_LISTEN_BACKLOG (5) +@@ unix-socket.h: struct unix_stream_listen_opts { #define UNIX_STREAM_LISTEN_OPTS_INIT \
{ \
- .listen_backlog_size = 5, \
- .force_unlink_before_bind = 1, \
+ .listen_backlog_size = DEFAULT_UNIX_STREAM_LISTEN_BACKLOG, \
+ .disallow_chdir = 0, \
}
- int unix_stream_connect(const char *path);
+-int unix_stream_connect(const char *path);
++int unix_stream_connect(const char *path, int disallow_chdir);
+ int unix_stream_listen(const char *path,
+ const struct unix_stream_listen_opts *opts);
+
-: ------------ > 10: b443e11ac32f unix-socket: create `unix_stream_server__listen_with_lock()`
14: 72c1c209c380 ! 11: 43c8db9a4468 simple-ipc: add Unix domain socket implementation
@@ compat/simple-ipc/ipc-unix-socket.c (new)
+ *pfd = -1;
+
+ for (k = 0; k < timeout_ms; k += wait_ms) {
-+ int fd = unix_stream_connect(path);
++ int fd = unix_stream_connect(path, options->uds_disallow_chdir);
+
+ if (fd != -1) {
+ *pfd = fd;
@@ compat/simple-ipc/ipc-unix-socket.c (new)
+
+ trace2_region_enter("ipc-client", "send-command", NULL);
+
-+ if (write_packetized_from_buf2(message, strlen(message),
-+ connection->fd, 1,
-+ &connection->scratch_write_buffer) < 0) {
++ if (write_packetized_from_buf_no_flush(message, strlen(message),
++ connection->fd) < 0 ||
++ packet_flush_gently(connection->fd) < 0) {
+ ret = error(_("could not send IPC command"));
+ goto done;
+ }
+
-+ if (read_packetized_to_strbuf(connection->fd, answer,
-+ PACKET_READ_NEVER_DIE) < 0) {
++ if (read_packetized_to_strbuf(
++ connection->fd, answer,
++ PACKET_READ_GENTLE_ON_EOF | PACKET_READ_NEVER_DIE) < 0) {
+ ret = error(_("could not read IPC response"));
+ goto done;
+ }
@@ compat/simple-ipc/ipc-unix-socket.c (new)
+ struct ipc_worker_thread_data *next_thread;
+ struct ipc_server_data *server_data;
+ pthread_t pthread_id;
-+ struct packet_scratch_space scratch_write_buffer;
+};
+
+struct ipc_accept_thread_data {
+ enum magic magic;
+ struct ipc_server_data *server_data;
+
-+ int fd_listen;
-+ struct stat st_listen;
++ struct unix_stream_server_socket *server_socket;
+
+ int fd_send_shutdown;
+ int fd_wait_shutdown;
@@ compat/simple-ipc/ipc-unix-socket.c (new)
+static int do_io_reply_callback(struct ipc_server_reply_data *reply_data,
+ const char *response, size_t response_len)
+{
-+ struct packet_scratch_space *scratch =
-+ &reply_data->worker_thread_data->scratch_write_buffer;
-+
+ if (reply_data->magic != MAGIC_SERVER_REPLY_DATA)
+ BUG("reply_cb called with wrong instance data");
+
-+ return write_packetized_from_buf2(response, response_len,
-+ reply_data->fd, 0, scratch);
++ return write_packetized_from_buf_no_flush(response, response_len,
++ reply_data->fd);
+}
+
+/* A randomly chosen value. */
@@ compat/simple-ipc/ipc-unix-socket.c (new)
+
+ reply_data.fd = fd;
+
-+ ret = read_packetized_to_strbuf(reply_data.fd, &buf,
-+ PACKET_READ_NEVER_DIE);
++ ret = read_packetized_to_strbuf(
++ reply_data.fd, &buf,
++ PACKET_READ_GENTLE_ON_EOF | PACKET_READ_NEVER_DIE);
+ if (ret >= 0) {
+ ret = worker_thread_data->server_data->application_cb(
+ worker_thread_data->server_data->application_data,
@@ compat/simple-ipc/ipc-unix-socket.c (new)
+ if (ret == SIMPLE_IPC_QUIT) {
+ trace2_data_string("ipc-worker", NULL, "queue_stop_async",
+ "application_quit");
-+ /* The application told us to shutdown. */
++ /*
++ * The application layer is telling the ipc-server
++ * layer to shutdown.
++ *
++ * We DO NOT have a response to send to the client.
++ *
++ * Queue an async stop (to stop the other threads) and
++ * allow this worker thread to exit now (no sense waiting
++ * for the thread-pool shutdown signal).
++ *
++ * Other non-idle worker threads are allowed to finish
++ * responding to their current clients.
++ */
+ ipc_server_stop_async(server_data);
+ break;
+ }
@@ compat/simple-ipc/ipc-unix-socket.c (new)
+ return NULL;
+}
+
-+/*
-+ * Return 1 if someone deleted or stole the on-disk socket from us.
-+ */
-+static int socket_was_stolen(struct ipc_accept_thread_data *accept_thread_data)
-+{
-+ struct stat st;
-+ struct stat *ref_st = &accept_thread_data->st_listen;
-+
-+ if (lstat(accept_thread_data->server_data->buf_path.buf, &st) == -1)
-+ return 1;
-+
-+ if (st.st_ino != ref_st->st_ino)
-+ return 1;
-+
-+ /* We might also consider the creation time on some platforms. */
-+
-+ return 0;
-+}
-+
+/* A randomly chosen value. */
+#define MY_ACCEPT_POLL_TIMEOUT_MS (60 * 1000)
+
@@ compat/simple-ipc/ipc-unix-socket.c (new)
+ pollfd[0].fd = accept_thread_data->fd_wait_shutdown;
+ pollfd[0].events = POLLIN;
+
-+ pollfd[1].fd = accept_thread_data->fd_listen;
++ pollfd[1].fd = accept_thread_data->server_socket->fd_socket;
+ pollfd[1].events = POLLIN;
+
+ result = poll(pollfd, 2, MY_ACCEPT_POLL_TIMEOUT_MS);
@@ compat/simple-ipc/ipc-unix-socket.c (new)
+
+ /*
+ * If someone deletes or force-creates a new unix
-+ * domain socket at out path, all future clients
++ * domain socket at our path, all future clients
+ * will be routed elsewhere and we silently starve.
+ * If that happens, just queue a shutdown.
+ */
-+ if (socket_was_stolen(
-+ accept_thread_data)) {
++ if (unix_stream_server__was_stolen(
++ accept_thread_data->server_socket)) {
+ trace2_data_string("ipc-accept", NULL,
+ "queue_stop_async",
+ "socket_stolen");
@@ compat/simple-ipc/ipc-unix-socket.c (new)
+ }
+
+ if (pollfd[1].revents & POLLIN) {
-+ /* a connection is available on fd_listen */
++ /* a connection is available on server_socket */
+
-+ int client_fd = accept(accept_thread_data->fd_listen,
-+ NULL, NULL);
++ int client_fd =
++ accept(accept_thread_data->server_socket->fd_socket,
++ NULL, NULL);
+ if (client_fd >= 0)
+ return client_fd;
+
@@ compat/simple-ipc/ipc-unix-socket.c (new)
+ */
+#define LISTEN_BACKLOG (50)
+
-+/*
-+ * Create a unix domain socket at the given path to listen for
-+ * client connections. The resulting socket will then appear
-+ * in the filesystem as an inode with S_IFSOCK. The inode is
-+ * itself created as part of the `bind(2)` operation.
-+ *
-+ * The term "socket" is ambiguous in this context. We want to open a
-+ * "socket-fd" that is bound to a "socket-inode" (path) on disk. We
-+ * listen on "socket-fd" for new connections and clients try to
-+ * open/connect using the "socket-inode" pathname.
-+ *
-+ * Unix domain sockets have a fundamental design flaw because the
-+ * "socket-inode" persists until the pathname is deleted; closing the
-+ * listening "socket-fd" only closes the socket handle/descriptor, it
-+ * does not delete the inode/pathname.
-+ *
-+ * Well-behaving service daemons are expected to also delete the inode
-+ * before shutdown. If a service crashes (or forgets) it can leave
-+ * the (now stale) inode in the filesystem. This behaves like a stale
-+ * ".lock" file and may prevent future service instances from starting
-+ * up correctly. (Because they won't be able to bind.)
-+ *
-+ * When future service instances try to create the listener socket,
-+ * `bind(2)` will fail with EADDRINUSE -- because the inode already
-+ * exists. However, the new instance cannot tell if it is a stale
-+ * inode *or* another service instance is already running.
-+ *
-+ * One possible solution is to blindly unlink the inode before
-+ * attempting to bind a new socket-fd and thus create a new
-+ * socket-inode. Then `bind(2)` should always succeed. However, if
-+ * there is an existing service instance, it would be orphaned -- it
-+ * would still be listening on a socket-fd that is still bound to an
-+ * (unlinked) socket-inode, but that socket-inode is no longer
-+ * associated with the pathname. New client connections will arrive
-+ * at OUR new socket-inode -- rather than the existing server's
-+ * socket. (I suppose it is up to the existing server to detect that
-+ * its socket-inode has been stolen and shutdown.)
-+ *
-+ * Another possible solution is to try to use the ".lock" trick, but
-+ * bind() does not have a exclusive-create use bit like open() does,
-+ * so we cannot have multiple servers fighting/racing to create the
-+ * same file name without having losers lose without knowing that they
-+ * lost.
-+ *
-+ * We try to avoid such stealing and would rather fail to run than
-+ * steal an existing socket-inode (because we assume that the
-+ * existing server has more context and value to the clients than a
-+ * freshly started server). However, if multiple servers are racing
-+ * to start, we don't care which one wins -- none of them have any
-+ * state information yet worth fighting for.
-+ *
-+ * Create a "unique" socket-inode (with our PID in it (and assume that
-+ * we can force-delete an existing socket with that name)). Stat it
-+ * to get the inode number and ctime -- so that we can identify it as
-+ * the one we created. Then use the atomic-rename trick to install it
-+ * in the real location. (This will unlink an existing socket with
-+ * that pathname -- and thereby steal the real socket-inode from an
-+ * existing server.)
-+ *
-+ * Elsewhere, our thread will periodically poll the socket-inode to
-+ * see if someone else steals ours.
-+ */
-+static int create_listener_socket(const char *path,
-+ const struct ipc_server_opts *ipc_opts,
-+ struct stat *st_socket)
++static struct unix_stream_server_socket *create_listener_socket(
++ const char *path,
++ const struct ipc_server_opts *ipc_opts)
+{
-+ struct stat st;
-+ struct strbuf buf_uniq = STRBUF_INIT;
-+ int fd_listen;
++ struct unix_stream_server_socket *server_socket = NULL;
+ struct unix_stream_listen_opts uslg_opts = UNIX_STREAM_LISTEN_OPTS_INIT;
+
-+ if (!lstat(path, &st) && S_ISSOCK(st.st_mode)) {
-+ int fd_client;
-+ /*
-+ * A socket-inode at `path` exists on disk, but we
-+ * don't know whether it belongs to an active server
-+ * or if the last server died without cleaning up.
-+ *
-+ * Poke it with a trivial connection to try to find out.
-+ */
-+ trace2_data_string("ipc-server", NULL, "try-detect-server",
-+ path);
-+ fd_client = unix_stream_connect(path);
-+ if (fd_client >= 0) {
-+ close(fd_client);
-+ errno = EADDRINUSE;
-+ return error_errno(_("socket already in use '%s'"),
-+ path);
-+ }
-+ }
-+
-+ /*
-+ * Create pathname to our "unique" socket and set it up for
-+ * business.
-+ */
-+ strbuf_addf(&buf_uniq, "%s.%d", path, getpid());
-+
+ uslg_opts.listen_backlog_size = LISTEN_BACKLOG;
-+ uslg_opts.force_unlink_before_bind = 1;
+ uslg_opts.disallow_chdir = ipc_opts->uds_disallow_chdir;
-+ fd_listen = unix_stream_listen(buf_uniq.buf, &uslg_opts);
-+ if (fd_listen < 0) {
-+ int saved_errno = errno;
-+ error_errno(_("could not create listener socket '%s'"),
-+ buf_uniq.buf);
-+ strbuf_release(&buf_uniq);
-+ errno = saved_errno;
-+ return -1;
-+ }
+
-+ if (lstat(buf_uniq.buf, st_socket)) {
-+ int saved_errno = errno;
-+ error_errno(_("could not stat listener socket '%s'"),
-+ buf_uniq.buf);
-+ close(fd_listen);
-+ unlink(buf_uniq.buf);
-+ strbuf_release(&buf_uniq);
-+ errno = saved_errno;
-+ return -1;
-+ }
++ server_socket = unix_stream_server__listen_with_lock(path, &uslg_opts);
++ if (!server_socket)
++ return NULL;
+
-+ if (set_socket_blocking_flag(fd_listen, 1)) {
++ if (set_socket_blocking_flag(server_socket->fd_socket, 1)) {
+ int saved_errno = errno;
+ error_errno(_("could not set listener socket nonblocking '%s'"),
-+ buf_uniq.buf);
-+ close(fd_listen);
-+ unlink(buf_uniq.buf);
-+ strbuf_release(&buf_uniq);
-+ errno = saved_errno;
-+ return -1;
-+ }
-+
-+ /*
-+ * Install it as the "real" socket so that clients will starting
-+ * connecting to our socket.
-+ */
-+ if (rename(buf_uniq.buf, path)) {
-+ int saved_errno = errno;
-+ error_errno(_("could not create listener socket '%s'"), path);
-+ close(fd_listen);
-+ unlink(buf_uniq.buf);
-+ strbuf_release(&buf_uniq);
++ path);
++ unix_stream_server__free(server_socket);
+ errno = saved_errno;
-+ return -1;
++ return NULL;
+ }
+
-+ strbuf_release(&buf_uniq);
-+ trace2_data_string("ipc-server", NULL, "try-listen", path);
-+ return fd_listen;
++ trace2_data_string("ipc-server", NULL, "listen-with-lock", path);
++ return server_socket;
+}
+
-+static int setup_listener_socket(const char *path, struct stat *st_socket,
-+ const struct ipc_server_opts *ipc_opts)
++static struct unix_stream_server_socket *setup_listener_socket(
++ const char *path,
++ const struct ipc_server_opts *ipc_opts)
+{
-+ int fd_listen;
++ struct unix_stream_server_socket *server_socket;
+
+ trace2_region_enter("ipc-server", "create-listener_socket", NULL);
-+ fd_listen = create_listener_socket(path, ipc_opts, st_socket);
++ server_socket = create_listener_socket(path, ipc_opts);
+ trace2_region_leave("ipc-server", "create-listener_socket", NULL);
+
-+ return fd_listen;
++ return server_socket;
+}
+
+/*
@@ compat/simple-ipc/ipc-unix-socket.c (new)
+ ipc_server_application_cb *application_cb,
+ void *application_data)
+{
++ struct unix_stream_server_socket *server_socket = NULL;
+ struct ipc_server_data *server_data;
-+ int fd_listen;
-+ struct stat st_listen;
+ int sv[2];
+ int k;
+ int nr_threads = opts->nr_threads;
@@ compat/simple-ipc/ipc-unix-socket.c (new)
+ path);
+ }
+
-+ fd_listen = setup_listener_socket(path, &st_listen, opts);
-+ if (fd_listen < 0) {
++ server_socket = setup_listener_socket(path, opts);
++ if (!server_socket) {
+ int saved_errno = errno;
+ close(sv[0]);
+ close(sv[1]);
@@ compat/simple-ipc/ipc-unix-socket.c (new)
+ xcalloc(1, sizeof(*server_data->accept_thread));
+ server_data->accept_thread->magic = MAGIC_ACCEPT_THREAD_DATA;
+ server_data->accept_thread->server_data = server_data;
-+ server_data->accept_thread->fd_listen = fd_listen;
-+ server_data->accept_thread->st_listen = st_listen;
++ server_data->accept_thread->server_socket = server_socket;
+ server_data->accept_thread->fd_send_shutdown = sv[0];
+ server_data->accept_thread->fd_wait_shutdown = sv[1];
+
@@ compat/simple-ipc/ipc-unix-socket.c (new)
+
+ accept_thread_data = server_data->accept_thread;
+ if (accept_thread_data) {
-+ if (accept_thread_data->fd_listen != -1) {
-+ /*
-+ * Only unlink the unix domain socket if we
-+ * created it. That is, if another daemon
-+ * process force-created a new socket at this
-+ * path, and effectively steals our path
-+ * (which prevents us from receiving any
-+ * future clients), we don't want to do the
-+ * same thing to them.
-+ */
-+ if (!socket_was_stolen(
-+ accept_thread_data))
-+ unlink(server_data->buf_path.buf);
++ unix_stream_server__free(accept_thread_data->server_socket);
+
-+ close(accept_thread_data->fd_listen);
-+ }
+ if (accept_thread_data->fd_send_shutdown != -1)
+ close(accept_thread_data->fd_send_shutdown);
+ if (accept_thread_data->fd_wait_shutdown != -1)
@@ simple-ipc.h
#define SUPPORTS_SIMPLE_IPC
#endif
+@@ simple-ipc.h: struct ipc_client_connect_options {
+ * the service and need to wait for it to become ready.
+ */
+ unsigned int wait_if_not_found:1;
++
++ /*
++ * Disallow chdir() when creating a Unix domain socket.
++ */
++ unsigned int uds_disallow_chdir:1;
+ };
+
+ #define IPC_CLIENT_CONNECT_OPTIONS_INIT { \
+ .wait_if_busy = 0, \
+ .wait_if_not_found = 0, \
++ .uds_disallow_chdir = 0, \
+ }
+
+ /*
@@ simple-ipc.h: struct ipc_server_data;
struct ipc_server_opts
{
9: f0bebf1cdb31 ! 12: 1e5c856ade85 simple-ipc: add t/helper/test-simple-ipc and t0052
@@ Metadata
Author: Jeff Hostetler <[email protected]>
## Commit message ##
- simple-ipc: add t/helper/test-simple-ipc and t0052
+ t0052: add simple-ipc tests and t/helper/test-simple-ipc tool
- Create unit tests for "simple-ipc". These are currently only enabled
- on Windows.
+ Create t0052-simple-ipc.sh with unit tests for the "simple-ipc" mechanism.
+
+ Create t/helper/test-simple-ipc test tool to exercise the "simple-ipc"
+ functions.
+
+ When the tool is invoked with "run-daemon", it runs a server to listen
+ for "simple-ipc" connections on a test socket or named pipe and
+ responds to a set of commands to exercise/stress the communication
+ setup.
+
+ When the tool is invoked with "start-daemon", it spawns a "run-daemon"
+ command in the background and waits for the server to become ready
+ before exiting. (This helps make unit tests in t0052 more predictable
+ and avoids the need for arbitrary sleeps in the test script.)
+
+ The tool also has a series of client "send" commands to send commands
+ and data to a server instance.
Signed-off-by: Jeff Hostetler <[email protected]>
@@ t/helper/test-simple-ipc.c (new)
+#include "simple-ipc.h"
+#include "parse-options.h"
+#include "thread-utils.h"
++#include "strvec.h"
+
+#ifndef SUPPORTS_SIMPLE_IPC
+int cmd__simple_ipc(int argc, const char **argv)
@@ t/helper/test-simple-ipc.c (new)
+
+ if (!strcmp(command, "quit")) {
+ /*
-+ * Tell ipc-server to hangup with an empty reply.
++ * The client sent a "quit" command. This is an async
++ * request for the server to shutdown.
++ *
++ * We DO NOT send the client a response message
++ * (because we have nothing to say and the other
++ * server threads have not yet stopped).
++ *
++ * Tell the ipc-server layer to start shutting down.
++ * This includes: stop listening for new connections
++ * on the socket/pipe and telling all worker threads
++ * to finish/drain their outgoing responses to other
++ * clients.
++ *
++ * This DOES NOT force an immediate sync shutdown.
+ */
+ return SIMPLE_IPC_QUIT;
+ }
@@ t/helper/test-simple-ipc.c (new)
+ };
+
+ const char * const daemon_usage[] = {
-+ N_("test-helper simple-ipc daemon [<options>"),
++ N_("test-helper simple-ipc run-daemon [<options>"),
+ NULL
+ };
+ struct option daemon_options[] = {
@@ t/helper/test-simple-ipc.c (new)
+ return ipc_server_run(path, &opts, test_app_cb, (void*)&my_app_data);
+}
+
++#ifndef GIT_WINDOWS_NATIVE
++/*
++ * This is adapted from `daemonize()`. Use `fork()` to directly create and
++ * run the daemon in a child process.
++ */
++static int spawn_server(const char *path,
++ const struct ipc_server_opts *opts,
++ pid_t *pid)
++{
++ *pid = fork();
++
++ switch (*pid) {
++ case 0:
++ if (setsid() == -1)
++ error_errno(_("setsid failed"));
++ close(0);
++ close(1);
++ close(2);
++ sanitize_stdfds();
++
++ return ipc_server_run(path, opts, test_app_cb, (void*)&my_app_data);
++
++ case -1:
++ return error_errno(_("could not spawn daemon in the background"));
++
++ default:
++ return 0;
++ }
++}
++#else
++/*
++ * Conceptually like `daemonize()` but different because Windows does not
++ * have `fork(2)`. Spawn a normal Windows child process but without the
++ * limitations of `start_command()` and `finish_command()`.
++ */
++static int spawn_server(const char *path,
++ const struct ipc_server_opts *opts,
++ pid_t *pid)
++{
++ char test_tool_exe[MAX_PATH];
++ struct strvec args = STRVEC_INIT;
++ int in, out;
++
++ GetModuleFileNameA(NULL, test_tool_exe, MAX_PATH);
++
++ in = open("/dev/null", O_RDONLY);
++ out = open("/dev/null", O_WRONLY);
++
++ strvec_push(&args, test_tool_exe);
++ strvec_push(&args, "simple-ipc");
++ strvec_push(&args, "run-daemon");
++ strvec_pushf(&args, "--threads=%d", opts->nr_threads);
++
++ *pid = mingw_spawnvpe(args.v[0], args.v, NULL, NULL, in, out, out);
++ close(in);
++ close(out);
++
++ strvec_clear(&args);
++
++ if (*pid < 0)
++ return error(_("could not spawn daemon in the background"));
++
++ return 0;
++}
++#endif
++
++/*
++ * This is adapted from `wait_or_whine()`. Watch the child process and
++ * let it get started and begin listening for requests on the socket
++ * before reporting our success.
++ */
++static int wait_for_server_startup(const char * path, pid_t pid_child,
++ int max_wait_sec)
++{
++ int status;
++ pid_t pid_seen;
++ enum ipc_active_state s;
++ time_t time_limit, now;
++
++ time(&time_limit);
++ time_limit += max_wait_sec;
++
++ for (;;) {
++ pid_seen = waitpid(pid_child, &status, WNOHANG);
++
++ if (pid_seen == -1)
++ return error_errno(_("waitpid failed"));
++
++ else if (pid_seen == 0) {
++ /*
++ * The child is still running (this should be
++ * the normal case). Try to connect to it on
++ * the socket and see if it is ready for
++ * business.
++ *
++ * If there is another daemon already running,
++ * our child will fail to start (possibly
++ * after a timeout on the lock), but we don't
++ * care (who responds) if the socket is live.
++ */
++ s = ipc_get_active_state(path);
++ if (s == IPC_STATE__LISTENING)
++ return 0;
++
++ time(&now);
++ if (now > time_limit)
++ return error(_("daemon not online yet"));
++
++ continue;
++ }
++
++ else if (pid_seen == pid_child) {
++ /*
++ * The new child daemon process shutdown while
++ * it was starting up, so it is not listening
++ * on the socket.
++ *
++ * Try to ping the socket in the odd chance
++ * that another daemon started (or was already
++ * running) while our child was starting.
++ *
++ * Again, we don't care who services the socket.
++ */
++ s = ipc_get_active_state(path);
++ if (s == IPC_STATE__LISTENING)
++ return 0;
++
++ /*
++ * We don't care about the WEXITSTATUS() nor
++ * any of the WIF*(status) values because
++ * `cmd__simple_ipc()` does the `!!result`
++ * trick on all function return values.
++ *
++ * So it is sufficient to just report the
++ * early shutdown as an error.
++ */
++ return error(_("daemon failed to start"));
++ }
++
++ else
++ return error(_("waitpid is confused"));
++ }
++}
++
++/*
++ * This process will start a simple-ipc server in a background process and
++ * wait for it to become ready. This is like `daemonize()` but gives us
++ * more control and better error reporting (and makes it easier to write
++ * unit tests).
++ */
++static int daemon__start_server(const char *path, int argc, const char **argv)
++{
++ pid_t pid_child;
++ int ret;
++ int max_wait_sec = 60;
++ struct ipc_server_opts opts = {
++ .nr_threads = 5
++ };
++
++ const char * const daemon_usage[] = {
++ N_("test-helper simple-ipc start-daemon [<options>"),
++ NULL
++ };
++
++ struct option daemon_options[] = {
++ OPT_INTEGER(0, "max-wait", &max_wait_sec,
++ N_("seconds to wait for daemon to startup")),
++ OPT_INTEGER(0, "threads", &opts.nr_threads,
++ N_("number of threads in server thread pool")),
++ OPT_END()
++ };
++
++ argc = parse_options(argc, argv, NULL, daemon_options, daemon_usage, 0);
++
++ if (max_wait_sec < 0)
++ max_wait_sec = 0;
++ if (opts.nr_threads < 1)
++ opts.nr_threads = 1;
++
++ /*
++ * Run the actual daemon in a background process.
++ */
++ ret = spawn_server(path, &opts, &pid_child);
++ if (pid_child <= 0)
++ return ret;
++
++ /*
++ * Let the parent wait for the child process to get started
++ * and begin listening for requests on the socket.
++ */
++ ret = wait_for_server_startup(path, pid_child, max_wait_sec);
++
++ return ret;
++}
++
+/*
+ * This process will run a quick probe to see if a simple-ipc server
+ * is active on this path.
@@ t/helper/test-simple-ipc.c (new)
+ options.wait_if_not_found = 0;
+
+ if (!ipc_client_send_command(path, &options, command, &buf)) {
-+ printf("%s\n", buf.buf);
-+ fflush(stdout);
++ if (buf.len) {
++ printf("%s\n", buf.buf);
++ fflush(stdout);
++ }
+ strbuf_release(&buf);
+
+ return 0;
@@ t/helper/test-simple-ipc.c (new)
+ * message can be sent and that the kernel or pkt-line layers will
+ * properly chunk it and that the daemon receives the entire message.
+ */
-+static int do_sendbytes(int bytecount, char byte, const char *path)
++static int do_sendbytes(int bytecount, char byte, const char *path,
++ const struct ipc_client_connect_options *options)
+{
+ struct strbuf buf_send = STRBUF_INIT;
+ struct strbuf buf_resp = STRBUF_INIT;
-+ struct ipc_client_connect_options options
-+ = IPC_CLIENT_CONNECT_OPTIONS_INIT;
-+
-+ options.wait_if_busy = 1;
-+ options.wait_if_not_found = 0;
+
+ strbuf_addstr(&buf_send, "sendbytes ");
+ strbuf_addchars(&buf_send, byte, bytecount);
+
-+ if (!ipc_client_send_command(path, &options, buf_send.buf, &buf_resp)) {
++ if (!ipc_client_send_command(path, options, buf_send.buf, &buf_resp)) {
+ strbuf_rtrim(&buf_resp);
+ printf("sent:%c%08d %s\n", byte, bytecount, buf_resp.buf);
+ fflush(stdout);
@@ t/helper/test-simple-ipc.c (new)
+ OPT_STRING(0, "byte", &string, N_("byte"), N_("ballast")),
+ OPT_END()
+ };
++ struct ipc_client_connect_options options
++ = IPC_CLIENT_CONNECT_OPTIONS_INIT;
++
++ options.wait_if_busy = 1;
++ options.wait_if_not_found = 0;
++ options.uds_disallow_chdir = 0;
+
+ argc = parse_options(argc, argv, NULL, sendbytes_options, sendbytes_usage, 0);
+
-+ return do_sendbytes(bytecount, string[0], path);
++ return do_sendbytes(bytecount, string[0], path, &options);
+}
+
+struct multiple_thread_data {
@@ t/helper/test-simple-ipc.c (new)
+{
+ struct multiple_thread_data *d = _multiple_thread_data;
+ int k;
++ struct ipc_client_connect_options options
++ = IPC_CLIENT_CONNECT_OPTIONS_INIT;
++
++ options.wait_if_busy = 1;
++ options.wait_if_not_found = 0;
++ /*
++ * A multi-threaded client should not be randomly calling chdir().
++ * The test will pass without this restriction because the test is
++ * not otherwise accessing the filesystem, but it makes us honest.
++ */
++ options.uds_disallow_chdir = 1;
+
+ trace2_thread_start("multiple");
+
+ for (k = 0; k < d->batchsize; k++) {
-+ if (do_sendbytes(d->bytecount + k, d->letter, d->path))
++ if (do_sendbytes(d->bytecount + k, d->letter, d->path, &options))
+ d->sum_errors++;
+ else
+ d->sum_good++;
@@ t/helper/test-simple-ipc.c (new)
+ if (argc == 2 && !strcmp(argv[1], "SUPPORTS_SIMPLE_IPC"))
+ return 0;
+
-+ /* Use '!!' on all dispatch functions to map from `error()` style
-+ * (returns -1) style to `test_must_fail` style (expects 1) and
-+ * get less confusing shell error messages.
++ /*
++ * Use '!!' on all dispatch functions to map from `error()` style
++ * (returns -1) style to `test_must_fail` style (expects 1). This
++ * makes shell error messages less confusing.
+ */
+
+ if (argc == 2 && !strcmp(argv[1], "is-active"))
+ return !!client__probe_server(path);
+
-+ if (argc >= 2 && !strcmp(argv[1], "daemon"))
++ if (argc >= 2 && !strcmp(argv[1], "run-daemon"))
+ return !!daemon__run_server(path, argc, argv);
+
++ if (argc >= 2 && !strcmp(argv[1], "start-daemon"))
++ return !!daemon__start_server(path, argc, argv);
++
+ /*
+ * Client commands follow. Ensure a server is running before
+ * going any further.
@@ t/t0052-simple-ipc.sh (new)
+}
+
+stop_simple_IPC_server () {
-+ test -n "$SIMPLE_IPC_PID" || return 0
-+
-+ kill "$SIMPLE_IPC_PID" &&
-+ SIMPLE_IPC_PID=
++ test-tool simple-ipc send quit
+}
+
+test_expect_success 'start simple command server' '
-+ { test-tool simple-ipc daemon --threads=8 & } &&
-+ SIMPLE_IPC_PID=$! &&
+ test_atexit stop_simple_IPC_server &&
-+
-+ sleep 1 &&
-+
++ test-tool simple-ipc start-daemon --threads=8 &&
+ test-tool simple-ipc is-active
+'
+
@@ t/t0052-simple-ipc.sh (new)
+'
+
+test_expect_success 'servers cannot share the same path' '
-+ test_must_fail test-tool simple-ipc daemon &&
++ test_must_fail test-tool simple-ipc run-daemon &&
+ test-tool simple-ipc is-active
+'
+
@@ t/t0052-simple-ipc.sh (new)
+ test_cmp expect_a actual_a
+'
+
++# Sending a "quit" message to the server causes it to start an "async
++# shutdown" -- queuing shutdown events to all socket/pipe thread-pool
++# threads. Each thread will process that event after finishing
++# (draining) any in-progress IO with other clients. So when the "send
++# quit" client command exits, the ipc-server may still be running (but
++# it should be cleaning up).
++#
++# So, insert a generous sleep here to give the server time to shutdown.
++#
+test_expect_success '`quit` works' '
+ test-tool simple-ipc send quit &&
++
++ sleep 5 &&
++
+ test_must_fail test-tool simple-ipc is-active &&
+ test_must_fail test-tool simple-ipc send ping
+' 13: 2cca15a10ece < -: ------------ unix-socket: do not call die in unix_stream_connect()
Signature · 1 line
gitgitgadget
PATCH v3 7/12 unix-socket: elimiate static unix_stream_socket() helper function
From: Jeff Hostetler <[email protected]>
The static helper function unix_stream_socket() calls die(). This is not appropriate for all callers. Eliminate the wrapper function and make the callers propagate the error.
--- unix-socket.c | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-)
unix-socket.c +13 -14
@@ -1,14 +1,6 @@#include "cache.h" #include "unix-socket.h" -static int unix_stream_socket (void ) -{ -int fd =socket (AF_UNIX ,SOCK_STREAM ,0 ); -if (fd <0 ) -die_errno ("unable to create socket" ); -return fd; -} -static int chdir_len (const char *orig,int len) {char *path =xmemdupz (orig, len);
@@ -73,13 +65,16 @@ static int unix_sockaddr_init(struct sockaddr_un *sa, const char *path,int unix_stream_connect (const char *path) { -int fd, saved_errno; +int fd =-1 , saved_errno;struct sockaddr_un sa;struct unix_sockaddr_context ctx;if (unix_sockaddr_init (&sa, path, &ctx) <0 )return -1 ; - fd =unix_stream_socket (); + fd =socket (AF_UNIX ,SOCK_STREAM ,0 ); +if (fd <0 ) + goto fail; +if (connect (fd, (struct sockaddr *)&sa,sizeof (sa)) <0 ) goto fail;unix_sockaddr_cleanup (&ctx);
@@ -87,15 +82,16 @@ int unix_stream_connect(const char *path) fail: saved_errno = errno; +if (fd !=-1 ) +close (fd);unix_sockaddr_cleanup (&ctx); -close (fd); errno = saved_errno;return -1 ; }int unix_stream_listen (const char *path) { -int fd, saved_errno; +int fd =-1 , saved_errno;struct sockaddr_un sa;struct unix_sockaddr_context ctx;
@@ -103,7 +99,9 @@ int unix_stream_listen(const char *path)if (unix_sockaddr_init (&sa, path, &ctx) <0 )return -1 ; - fd =unix_stream_socket (); + fd =socket (AF_UNIX ,SOCK_STREAM ,0 ); +if (fd <0 ) + goto fail;if (bind (fd, (struct sockaddr *)&sa,sizeof (sa)) <0 ) goto fail;
@@ -116,8 +114,9 @@ int unix_stream_listen(const char *path) fail: saved_errno = errno; +if (fd !=-1 ) +close (fd);unix_sockaddr_cleanup (&ctx); -close (fd); errno = saved_errno;return -1 ; }
Signature · 1 line
gitgitgadget
PATCH v3 8/12 unix-socket: add backlog size option to unix_stream_listen()
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Update unix_stream_listen() to take an options structure to override default behaviors. This commit includes the size of the listen() backlog.
--- builtin/credential-cache--daemon.c | 3 ++- unix-socket.c | 9 +++++++-- unix-socket.h | 14 +++++++++++++- 3 files changed, 22 insertions(+), 4 deletions(-)
builtin/credential-cache--daemon.c +2 -1
@@ -203,9 +203,10 @@ static int serve_cache_loop(int fd)static void serve_cache (const char *socket_path,int debug) { +struct unix_stream_listen_opts opts =UNIX_STREAM_LISTEN_OPTS_INIT ;int fd; - fd =unix_stream_listen (socket_path); + fd =unix_stream_listen (socket_path, &opts);if (fd <0 )die_errno ("unable to bind to '%s'" , socket_path);
unix-socket.c +7 -2
@@ -89,9 +89,11 @@ int unix_stream_connect(const char *path)return -1 ; } -int unix_stream_listen (const char *path) +int unix_stream_listen (const char *path, +const struct unix_stream_listen_opts *opts) {int fd =-1 , saved_errno; +int backlog;struct sockaddr_un sa;struct unix_sockaddr_context ctx;
@@ -106,7 +108,10 @@ int unix_stream_listen(const char *path)if (bind (fd, (struct sockaddr *)&sa,sizeof (sa)) <0 ) goto fail; -if (listen (fd,5 ) <0 ) + backlog = opts->listen_backlog_size; +if (backlog <=0 ) + backlog =DEFAULT_UNIX_STREAM_LISTEN_BACKLOG ; +if (listen (fd, backlog) <0 ) goto fail;unix_sockaddr_cleanup (&ctx);
unix-socket.h +13 -1
@@ -1,7 +1,19 @@#ifndef UNIX_SOCKET_H #define UNIX_SOCKET_H +struct unix_stream_listen_opts { +int listen_backlog_size; +}; + +#define DEFAULT_UNIX_STREAM_LISTEN_BACKLOG (5) + +#define UNIX_STREAM_LISTEN_OPTS_INIT \ +{ \ + .listen_backlog_size = DEFAULT_UNIX_STREAM_LISTEN_BACKLOG, \ +} +int unix_stream_connect (const char *path); -int unix_stream_listen (const char *path); +int unix_stream_listen (const char *path, +const struct unix_stream_listen_opts *opts);#endif /* UNIX_SOCKET_H */
Signature · 1 line
gitgitgadget
PATCH v3 10/12 unix-socket: create `unix_stream_server__listen_with_lock()`
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Create a version of unix_stream_listen() that uses a ".lock" lockfile to create the unix domain socket in a race-free manner.
Unix domain sockets have a fundamental problem on Unix systems because they persist in the filesystem until they are deleted. This is independent of whether a server is actually listening for connections. Well-behaved servers are expected to delete the socket when they shutdown. A new server cannot easily tell if a found socket is attached to an active server or is leftover cruft from a dead server. The traditional solution used by unix_stream_listen() is to force delete the socket pathname and then create a new socket. This solves the latter (cruft) problem, but in the case of the former, it orphans the existing server (by stealing the pathname associated with the socket it is listening on).
We cannot directly use a .lock lockfile to create the socket because the socket is created by bind(2) rather than the open(2) mechanism used by tempfile.c.
As an alternative, we hold a plain lockfile ("<path>.lock") as a mutual exclusion device. Under the lock, we test if an existing socket ("<path>") is has an active server. If not, create a new socket and begin listening. Then we rollback the lockfile in all cases.
--- unix-socket.c | 115 ++++++++++++++++++++++++++++++++++++++++++++++++++ unix-socket.h | 29 +++++++++++++ 2 files changed, 144 insertions(+)
unix-socket.c +115 -0
@@ -1,4 +1,5 @@#include "cache.h" +#include "lockfile.h" #include "unix-socket.h" static int chdir_len (const char *orig,int len)
@@ -132,3 +133,117 @@ int unix_stream_listen(const char *path, errno = saved_errno;return -1 ; } + +static int is_another_server_alive (const char *path, +const struct unix_stream_listen_opts *opts) +{ +struct stat st; +int fd; + +if (!lstat (path, &st) &&S_ISSOCK (st.st_mode)) { +/* +* A socket-inode exists on disk at `path`, but we +* don't know whether it belongs to an active server +* or whether the last server died without cleaning +* up. +* +* Poke it with a trivial connection to try to find +* out. +*/ + fd =unix_stream_connect (path, opts->disallow_chdir); +if (fd >=0 ) { +close (fd); +return 1 ; + } + } + +return 0 ; +} + +struct unix_stream_server_socket *unix_stream_server__listen_with_lock ( +const char *path, +const struct unix_stream_listen_opts *opts) +{ +struct lock_file lock =LOCK_INIT ; +int fd_socket; +struct unix_stream_server_socket *server_socket; + +/* +* Create a lock at "<path>.lock" if we can. +*/ +if (hold_lock_file_for_update_timeout (&lock, path,0 , + opts->timeout_ms) <0 ) { +error_errno (_ ("could not lock listener socket '%s'" ), path); +return NULL ; + } + +/* +* If another server is listening on "<path>" give up. We do not +* want to create a socket and steal future connections from them. +*/ +if (is_another_server_alive (path, opts)) { + errno =EADDRINUSE ; +error_errno (_ ("listener socket already in use '%s'" ), path); +rollback_lock_file (&lock); +return NULL ; + } + +/* +* Create and bind to a Unix domain socket at "<path>". +*/ + fd_socket =unix_stream_listen (path, opts); +if (fd_socket <0 ) { +error_errno (_ ("could not create listener socket '%s'" ), path); +rollback_lock_file (&lock); +return NULL ; + } + + server_socket =xcalloc (1 ,sizeof (*server_socket)); + server_socket->path_socket =strdup (path); + server_socket->fd_socket = fd_socket; +lstat (path, &server_socket->st_socket); + +/* +* Always rollback (just delete) "<path>.lock" because we already created +* "<path>" as a socket and do not want to commit_lock to do the atomic +* rename trick. +*/ +rollback_lock_file (&lock); + +return server_socket; +} + +void unix_stream_server__free ( +struct unix_stream_server_socket *server_socket) +{ +if (!server_socket) +return ; + +if (server_socket->fd_socket >=0 ) { +if (!unix_stream_server__was_stolen (server_socket)) +unlink (server_socket->path_socket); +close (server_socket->fd_socket); + } + +free (server_socket->path_socket); +free (server_socket); +} + +int unix_stream_server__was_stolen ( +struct unix_stream_server_socket *server_socket) +{ +struct stat st_now; + +if (!server_socket) +return 0 ; + +if (lstat (server_socket->path_socket, &st_now) ==-1 ) +return 1 ; + +if (st_now.st_ino != server_socket->st_socket.st_ino) +return 1 ; + +/* We might also consider the ctime on some platforms. */ + +return 0 ; +}
unix-socket.h +29 -0
@@ -2,14 +2,17 @@#define UNIX_SOCKET_H struct unix_stream_listen_opts { +long timeout_ms;int listen_backlog_size;unsigned int disallow_chdir:1 ; }; +#define DEFAULT_UNIX_STREAM_LISTEN_TIMEOUT (100)#define DEFAULT_UNIX_STREAM_LISTEN_BACKLOG (5)#define UNIX_STREAM_LISTEN_OPTS_INIT \ { \ + .timeout_ms = DEFAULT_UNIX_STREAM_LISTEN_TIMEOUT, \ .listen_backlog_size = DEFAULT_UNIX_STREAM_LISTEN_BACKLOG, \ .disallow_chdir = 0, \ }
@@ -18,4 +21,30 @@ int unix_stream_connect(const char *path, int disallow_chdir);int unix_stream_listen (const char *path,const struct unix_stream_listen_opts *opts); +struct unix_stream_server_socket { +char *path_socket; +struct stat st_socket; +int fd_socket; +}; + +/* +* Create a Unix Domain Socket at the given path under the protection +* of a '.lock' lockfile. +*/ +struct unix_stream_server_socket *unix_stream_server__listen_with_lock ( +const char *path, +const struct unix_stream_listen_opts *opts); + +/* +* Close and delete the socket. +*/ +void unix_stream_server__free ( +struct unix_stream_server_socket *server_socket); + +/* +* Return 1 if the inode of the pathname to our socket changes. +*/ +int unix_stream_server__was_stolen ( +struct unix_stream_server_socket *server_socket); +#endif /* UNIX_SOCKET_H */
Signature · 1 line
gitgitgadget
PATCH v3 9/12 unix-socket: disallow chdir() when creating unix domain sockets
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Calls to chdir() are dangerous in a multi-threaded context. If unix_stream_listen() or unix_stream_connect() is given a socket pathname that is too long to fit in a sockaddr_un structure, it will chdir() to the parent directory of the requested socket pathname, create the socket using a relative pathname, and then chdir() back. This is not thread-safe.
Teach unix_sockaddr_init() to not allow calls to chdir() when this flag is set.
--- builtin/credential-cache.c | 2 +- unix-socket.c | 17 ++++++++++++----- unix-socket.h | 4 +++- 3 files changed, 16 insertions(+), 7 deletions(-)
builtin/credential-cache.c +1 -1
@@ -14,7 +14,7 @@static int send_request (const char *socket,const struct strbuf *out) {int got_data =0 ; -int fd =unix_stream_connect (socket); +int fd =unix_stream_connect (socket,0 );if (fd <0 )return -1 ;
unix-socket.c +12 -5
@@ -28,16 +28,23 @@ static void unix_sockaddr_cleanup(struct unix_sockaddr_context *ctx) }static int unix_sockaddr_init (struct sockaddr_un *sa,const char *path, -struct unix_sockaddr_context *ctx) +struct unix_sockaddr_context *ctx, +int disallow_chdir) {int size =strlen (path) +1 ; ctx->orig_dir =NULL ;if (size >sizeof (sa->sun_path)) { -const char *slash =find_last_dir_sep (path); +const char *slash;const char *dir;struct strbuf cwd =STRBUF_INIT ; +if (disallow_chdir) { + errno =ENAMETOOLONG ; +return -1 ; + } + + slash =find_last_dir_sep (path);if (!slash) { errno =ENAMETOOLONG ;return -1 ;
@@ -63,13 +70,13 @@ static int unix_sockaddr_init(struct sockaddr_un *sa, const char *path,return 0 ; } -int unix_stream_connect (const char *path) +int unix_stream_connect (const char *path,int disallow_chdir) {int fd =-1 , saved_errno;struct sockaddr_un sa;struct unix_sockaddr_context ctx; -if (unix_sockaddr_init (&sa, path, &ctx) <0 ) +if (unix_sockaddr_init (&sa, path, &ctx, disallow_chdir) <0 )return -1 ; fd =socket (AF_UNIX ,SOCK_STREAM ,0 );if (fd <0 )
@@ -99,7 +106,7 @@ int unix_stream_listen(const char *path,unlink (path); -if (unix_sockaddr_init (&sa, path, &ctx) <0 ) +if (unix_sockaddr_init (&sa, path, &ctx, opts->disallow_chdir) <0 )return -1 ; fd =socket (AF_UNIX ,SOCK_STREAM ,0 );if (fd <0 )
unix-socket.h +3 -1
@@ -3,6 +3,7 @@struct unix_stream_listen_opts {int listen_backlog_size; +unsigned int disallow_chdir:1 ; };#define DEFAULT_UNIX_STREAM_LISTEN_BACKLOG (5)
@@ -10,9 +11,10 @@ struct unix_stream_listen_opts {#define UNIX_STREAM_LISTEN_OPTS_INIT \ { \ .listen_backlog_size = DEFAULT_UNIX_STREAM_LISTEN_BACKLOG, \ + .disallow_chdir = 0, \ } -int unix_stream_connect (const char *path); +int unix_stream_connect (const char *path,int disallow_chdir);int unix_stream_listen (const char *path,const struct unix_stream_listen_opts *opts);
Signature · 1 line
gitgitgadget
PATCH v3 12/12 t0052: add simple-ipc tests and t/helper/test-simple-ipc tool
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Create t0052-simple-ipc.sh with unit tests for the "simple-ipc" mechanism.
Create t/helper/test-simple-ipc test tool to exercise the "simple-ipc" functions.
When the tool is invoked with "run-daemon", it runs a server to listen for "simple-ipc" connections on a test socket or named pipe and responds to a set of commands to exercise/stress the communication setup.
When the tool is invoked with "start-daemon", it spawns a "run-daemon" command in the background and waits for the server to become ready before exiting. (This helps make unit tests in t0052 more predictable and avoids the need for arbitrary sleeps in the test script.)
The tool also has a series of client "send" commands to send commands and data to a server instance.
--- Makefile | 1 + t/helper/test-simple-ipc.c | 713 +++++++++++++++++++++++++++++++++++++ t/helper/test-tool.c | 1 + t/helper/test-tool.h | 1 + t/t0052-simple-ipc.sh | 134 +++++++ 5 files changed, 850 insertions(+) create mode 100644 t/helper/test-simple-ipc.c create mode 100755 t/t0052-simple-ipc.sh
Makefile +1 -0
@@ -740,6 +740,7 @@ TEST_BUILTINS_OBJS += test-serve-v2.o TEST_BUILTINS_OBJS += test-sha1.o TEST_BUILTINS_OBJS += test-sha256.o TEST_BUILTINS_OBJS += test-sigchain.o +TEST_BUILTINS_OBJS += test-simple-ipc.o TEST_BUILTINS_OBJS += test-strcmp-offset.o TEST_BUILTINS_OBJS += test-string-list.o TEST_BUILTINS_OBJS += test-submodule-config.o
t/helper/test-simple-ipc.c new file +713 -0
713 lines not on this page · raw message
t/helper/test-tool.c +1 -0
@@ -64,6 +64,7 @@ static struct test_cmd cmds[] = { { "sha1", cmd__sha1 }, { "sha256", cmd__sha256 }, { "sigchain", cmd__sigchain }, + { "simple-ipc", cmd__simple_ipc }, { "strcmp-offset", cmd__strcmp_offset }, { "string-list", cmd__string_list }, { "submodule-config", cmd__submodule_config },
t/helper/test-tool.h +1 -0
@@ -54,6 +54,7 @@ int cmd__sha1(int argc, const char **argv);int cmd__oid_array (int argc,const char **argv);int cmd__sha256 (int argc,const char **argv);int cmd__sigchain (int argc,const char **argv); +int cmd__simple_ipc (int argc,const char **argv);int cmd__strcmp_offset (int argc,const char **argv);int cmd__string_list (int argc,const char **argv);int cmd__submodule_config (int argc,const char **argv);
t/t0052-simple-ipc.sh new file +134 -0
134 lines not on this page · raw message
Signature · 1 line
gitgitgadget
PATCH v3 11/12 simple-ipc: add Unix domain socket implementation
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Create Unix domain socket based implementation of "simple-ipc".
A set of ipc_client routines implement a client library to connect to an ipc_server over a Unix domain socket, send a simple request, and receive a single response. Clients use blocking IO on the socket.
A set of ipc_server routines implement a thread pool to listen for and concurrently service client connections.
The server creates a new Unix domain socket at a known location. If a socket already exists with that name, the server tries to determine if another server is already listening on the socket or if the socket is dead. If socket is busy, the server exits with an error rather than stealing the socket. If the socket is dead, the server creates a new one and starts up.
If while running, the server detects that its socket has been stolen by another server, it automatically exits.
--- Makefile | 2 + compat/simple-ipc/ipc-unix-socket.c | 979 ++++++++++++++++++++++++++++ contrib/buildsystems/CMakeLists.txt | 2 + simple-ipc.h | 13 +- 4 files changed, 995 insertions(+), 1 deletion(-) create mode 100644 compat/simple-ipc/ipc-unix-socket.c
Makefile +2 -0
@@ -1677,6 +1677,8 @@ ifdef NO_UNIX_SOCKETS BASIC_CFLAGS += -DNO_UNIX_SOCKETS else LIB_OBJS += unix-socket.o + LIB_OBJS += compat/simple-ipc/ipc-shared.o + LIB_OBJS += compat/simple-ipc/ipc-unix-socket.o endif ifdef USE_WIN32_IPC
compat/simple-ipc/ipc-unix-socket.c new file +979 -0
979 lines not on this page · raw message
contrib/buildsystems/CMakeLists.txt +2 -0
@@ -248,6 +248,8 @@ endif() if(CMAKE_SYSTEM_NAME STREQUAL "Windows") list(APPEND compat_SOURCES compat/simple-ipc/ipc-shared.c compat/simple-ipc/ipc-win32.c) +else() + list(APPEND compat_SOURCES compat/simple-ipc/ipc-shared.c compat/simple-ipc/ipc-unix-socket.c) endif() set(EXE_EXTENSION ${CMAKE_EXECUTABLE_SUFFIX})
simple-ipc.h +12 -1
@@ -5,7 +5,7 @@* See Documentation/technical/api-simple-ipc.txt */ -#if defined(GIT_WINDOWS_NATIVE ) +#if defined(GIT_WINDOWS_NATIVE ) || !defined(NO_UNIX_SOCKETS )#define SUPPORTS_SIMPLE_IPC #endif
@@ -62,11 +62,17 @@ struct ipc_client_connect_options {* the service and need to wait for it to become ready. */ unsigned int wait_if_not_found:1; + +/* +* Disallow chdir() when creating a Unix domain socket. +*/ + unsigned int uds_disallow_chdir:1; };#define IPC_CLIENT_CONNECT_OPTIONS_INIT { \ .wait_if_busy = 0, \ .wait_if_not_found = 0, \ + .uds_disallow_chdir = 0, \ }/*
@@ -159,6 +165,11 @@ struct ipc_server_data;struct ipc_server_opts {int nr_threads; + +/* +* Disallow chdir() when creating a Unix domain socket. +*/ +unsigned int uds_disallow_chdir:1 ; };/*
Signature · 1 line
gitgitgadget
Re: [PATCH v3 12/12] t0052: add simple-ipc tests and t/helper/test-simple-ipc tool
In reply to Jeff Hostetler via GitGitGadgetQuoted 28 lines · Jeff Hostetler via GitGitGadget
From: Jeff Hostetler <[email protected]>
Create
t0052-simple-ipc.shwith unit tests for the "simple-ipc" mechanism.Create t/helper/test-simple-ipc test tool to exercise the "simple-ipc" functions.
When the tool is invoked with "run-daemon", it runs a server to listen for "simple-ipc" connections on a test socket or named pipe and responds to a set of commands to exercise/stress the communication setup.
When the tool is invoked with "start-daemon", it spawns a "run-daemon" command in the background and waits for the server to become ready before exiting. (This helps make unit tests in t0052 more predictable and avoids the need for arbitrary sleeps in the test script.)
The tool also has a series of client "send" commands to send commands and data to a server instance.
Signed-off-by Jeff Hostetler <[email protected]>---
t/helper/test-simple-ipc.c new file +0
-0
[...]
Quoted 3 lines
+/*
+ * This is "application callback" that sits on top of the "ipc-server".
+ * It completely defines the set of command verbs supported by this
Please avoid the noiseword "verbs" and just call them commands; a few of these commands are not even verbs.
Quoted 55 lines
+ * application.
+ */t/helper/test-simple-ipc.c new file +53
-0+static int test_app_cb (void *application_data, +const char *command, +ipc_server_reply_cb *reply_cb, +struct ipc_server_reply_data *reply_data) +{ +/* +* Verify that we received the application-data that we passed +* when we started the ipc-server. (We have several layers of +* callbacks calling callbacks and it's easy to get things mixed +* up (especially when some are "void*").) +*/ +if (application_data != (void *)&my_app_data) +BUG ("application_cb: application_data pointer wrong" ); + +if (!strcmp (command,"quit" )) { +/* +* The client sent a "quit" command. This is an async +* request for the server to shutdown. +* +* We DO NOT send the client a response message +* (because we have nothing to say and the other +* server threads have not yet stopped). +* +* Tell the ipc-server layer to start shutting down. +* This includes: stop listening for new connections +* on the socket/pipe and telling all worker threads +* to finish/drain their outgoing responses to other +* clients. +* +* This DOES NOT force an immediate sync shutdown. +*/ +return SIMPLE_IPC_QUIT ; + } + +if (!strcmp (command,"ping" )) { +const char *answer ="pong" ; +return reply_cb (reply_data, answer,strlen (answer)); + } + +if (!strcmp (command,"big" )) +return app__big_command (reply_cb, reply_data); + +if (!strcmp (command,"chunk" )) +return app__chunk_command (reply_cb, reply_data); + +if (!strcmp (command,"slow" )) +return app__slow_command (reply_cb, reply_data); + +if (starts_with (command,"sendbytes " )) +return app__sendbytes_command (command, reply_cb, reply_data); + +return app__unhandled_command (command, reply_cb, reply_data); +}
[...]
t/helper/test-simple-ipc.c
Quoted 3 lines
t/helper/test-simple-ipc.c new file +3
-0+int cmd__simple_ipc (int argc,const char **argv) +{ +const char *path ="ipc-test" ;
Since the path of the socket used in the tests is hardcoded, we could use it in the tests as well to check its presence/absence.
[...]
Quoted 6 lines
t/t0052-simple-ipc.sh new file +0
-0@@ -0,0 +1,134 @@
[...]
t/t0052-simple-ipc.sh
Quoted 13 lines
t/t0052-simple-ipc.sh new file +13
-0+# Sending a "quit" message to the server causes it to start an "async +# shutdown" -- queuing shutdown events to all socket/pipe thread-pool +# threads. Each thread will process that event after finishing +# (draining) any in-progress IO with other clients. So when the "send +# quit" client command exits, the ipc-server may still be running (but +# it should be cleaning up). +# +# So, insert a generous sleep here to give the server time to shutdown. +# +test_expect_success '`quit` works' ' + test-tool simple-ipc send quit && + + sleep 5 &&
The server process is responsible for removing the socket, so instead of a hard-coded 5 seconds delay the test could (semi-)busy wait in a loop until the socket disappears like this:
t/t0052-simple-ipc.sh +7 -0
@@ -122,6 +122,13 @@ test_expect_success 'stress test threads' ' test_expect_success '`quit` works' ' test-tool simple-ipc send quit && + nr_tries_left=10 && + while test -S ipc-test && + test $nr_tries_left -gt 0 + do + sleep 1 + nr_tries_left=$(($nr_tries_left - 1)) + done && test_must_fail test-tool simple-ipc is-active && test_must_fail test-tool simple-ipc send ping '
This way we might get away without any delay or with only a single one-second sleep in most cases, while we could bump the timeout a bit higher for the sake of a CI system in a particularly bad mood.
Would this work on Windows, or at least could it be tweaked to work there?
I think this is conceptually the same as what you did at startup, except in this example the test script waits instead of the test-tool subcommand. Perhaps it would be worth incorporating this wait into the test-tool as well; or perhaps it would be simpler to do the waiting in the test script at startup as well.
Quoted 7 lines
t/t0052-simple-ipc.sh new file +5
-0+ test_must_fail test-tool simple-ipc is-active && + test_must_fail test-tool simple-ipc send ping +' + +test_doneSignature · 1 line
gitgitgadget
Re: [PATCH v3 12/12] t0052: add simple-ipc tests and t/helper/test-simple-ipc tool
On 2/13/21 4:30 AM, SZEDER Gábor wrote:
[...]
Quoted 61 lines
[...]
Review
Quoted 3 lines
(quoted diff) +3
-0+int cmd__simple_ipc(int argc, const char **argv) +{ + const char *path = "ipc-test";Since the path of the socket used in the tests is hardcoded, we could use it in the tests as well to check its presence/absence.
[...]
Quoted 6 lines
t/t0052-simple-ipc.sh new file +0
-0@@ -0,0 +1,134 @@[...]
t/t0052-simple-ipc.sh
Quoted 13 lines
t/t0052-simple-ipc.sh new file +13
-0+# Sending a "quit" message to the server causes it to start an "async +# shutdown" -- queuing shutdown events to all socket/pipe thread-pool +# threads. Each thread will process that event after finishing +# (draining) any in-progress IO with other clients. So when the "send +# quit" client command exits, the ipc-server may still be running (but +# it should be cleaning up). +# +# So, insert a generous sleep here to give the server time to shutdown. +# +test_expect_success '`quit` works' ' + test-tool simple-ipc send quit && + + sleep 5 &&The server process is responsible for removing the socket, so instead of a hard-coded 5 seconds delay the test could (semi-)busy wait in a loop until the socket disappears like this:
t/t0052-simple-ipc.sh +7
-0@@ -122,6 +122,13 @@ test_expect_success 'stress test threads' ' test_expect_success '`quit` works' ' test-tool simple-ipc send quit && + nr_tries_left=10 && + while test -S ipc-test && + test $nr_tries_left -gt 0 + do + sleep 1 + nr_tries_left=$(($nr_tries_left - 1)) + done && test_must_fail test-tool simple-ipc is-active && test_must_fail test-tool simple-ipc send ping 'This way we might get away without any delay or with only a single one-second sleep in most cases, while we could bump the timeout a bit higher for the sake of a CI system in a particularly bad mood.
Quoted 8 lines
Would this work on Windows, or at least could it be tweaked to work there?
I think this is conceptually the same as what you did at startup, except in this example the test script waits instead of the test-tool subcommand. Perhaps it would be worth incorporating this wait into the test-tool as well; or perhaps it would be simpler to do the waiting in the test script at startup as well.
Thanks for the suggestions. Let me take another pass at it. I think making the "send quit" command try to wait until the server shutdown would make it easier for all concerned.
Quoted 8 lines
Quoted 8 lines
t/t0052-simple-ipc.sh new file +5
-0+ test_must_fail test-tool simple-ipc is-active && + test_must_fail test-tool simple-ipc send ping +' + +test_doneSignature · 1 line
gitgitgadget
v4 cover Simple IPC Mechanism
In reply to Jeff Hostetler via GitGitGadgetHere is V4 of my "Simple IPC" series. It addresses Gábor's comment WRT shutting down the server to make unit tests more predictable on CI servers.
(https://lore.kernel.org/git/[email protected])
Jeff
cc: Ævar Arnfjörð Bjarmason [email protected] cc: Jeff Hostetler [email protected] cc: Jeff King [email protected] cc: Chris Torek [email protected]
Jeff Hostetler (9):
pkt-line: eliminate the need for static buffer in
packet_write_gently()
simple-ipc: design documentation for new IPC mechanism
simple-ipc: add win32 implementation
unix-socket: elimiate static unix_stream_socket() helper function
unix-socket: add backlog size option to unix_stream_listen()
unix-socket: disallow chdir() when creating unix domain sockets
unix-socket: create unix_stream_server__listen_with_lock()
simple-ipc: add Unix domain socket implementation
t0052: add simple-ipc tests and t/helper/test-simple-ipc tool
Johannes Schindelin (3):
pkt-line: do not issue flush packets in write_packetized_*()
pkt-line: (optionally) libify the packet readers
pkt-line: add options argument to read_packetized_to_strbuf()
Documentation/technical/api-simple-ipc.txt | 34 + Makefile | 8 + builtin/credential-cache--daemon.c | 3 +- builtin/credential-cache.c | 2 +- compat/simple-ipc/ipc-shared.c | 28 + compat/simple-ipc/ipc-unix-socket.c | 979 +++++++++++++++++++++ compat/simple-ipc/ipc-win32.c | 749 ++++++++++++++++ config.mak.uname | 2 + contrib/buildsystems/CMakeLists.txt | 6 + convert.c | 16 +- pkt-line.c | 57 +- pkt-line.h | 20 +- simple-ipc.h | 235 +++++ t/helper/test-simple-ipc.c | 773 ++++++++++++++++ t/helper/test-tool.c | 1 + t/helper/test-tool.h | 1 + t/t0052-simple-ipc.sh | 122 +++ unix-socket.c | 168 +++- unix-socket.h | 47 +- 19 files changed, 3198 insertions(+), 53 deletions(-) create mode 100644 Documentation/technical/api-simple-ipc.txt create mode 100644 compat/simple-ipc/ipc-shared.c create mode 100644 compat/simple-ipc/ipc-unix-socket.c create mode 100644 compat/simple-ipc/ipc-win32.c create mode 100644 simple-ipc.h create mode 100644 t/helper/test-simple-ipc.c create mode 100755 t/t0052-simple-ipc.sh
base-commit: 773e25afc41b1b6533fa9ae2cd825d0b4a697fad
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-766%2Fjeffhostetler%2Fsimple-ipc-v4
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-766/jeffhostetler/simple-ipc-v4
Pull-Request: https://github.com/gitgitgadget/git/pull/766
Range-diff vs v3:
1: 2d6858b1625a = 1: 2d6858b1625a pkt-line: eliminate the need for static buffer in packet_write_gently()
2: 91a9f63d6692 = 2: 91a9f63d6692 pkt-line: do not issue flush packets in write_packetized_*()
3: e05467def4e1 = 3: e05467def4e1 pkt-line: (optionally) libify the packet readers
4: 81e14bed955c = 4: 81e14bed955c pkt-line: add options argument to read_packetized_to_strbuf()
5: 22eec60761a8 = 5: 22eec60761a8 simple-ipc: design documentation for new IPC mechanism
6: 171ec43ecfa4 = 6: 171ec43ecfa4 simple-ipc: add win32 implementation
7: b368318e6a23 = 7: b368318e6a23 unix-socket: elimiate static unix_stream_socket() helper function
8: 985b2e02b2df = 8: 985b2e02b2df unix-socket: add backlog size option to unix_stream_listen()
9: 1bfa36409d07 = 9: 1bfa36409d07 unix-socket: disallow chdir() when creating unix domain sockets
10: b443e11ac32f = 10: b443e11ac32f unix-socket: create `unix_stream_server__listen_with_lock()`
11: 43c8db9a4468 = 11: 43c8db9a4468 simple-ipc: add Unix domain socket implementation
12: 1e5c856ade85 ! 12: 09568a6500dd t0052: add simple-ipc tests and t/helper/test-simple-ipc tool
@@ t/helper/test-simple-ipc.c (new)
+static ipc_server_application_cb test_app_cb;
+
+/*
-+ * This is "application callback" that sits on top of the "ipc-server".
-+ * It completely defines the set of command verbs supported by this
-+ * application.
++ * This is the "application callback" that sits on top of the
++ * "ipc-server". It completely defines the set of commands supported
++ * by this application.
+ */
+static int test_app_cb(void *application_data,
+ const char *command,
@@ t/helper/test-simple-ipc.c (new)
+ * Send an IPC command to an already-running server daemon and print the
+ * response.
+ *
-+ * argv[2] contains a simple (1 word) command verb that `test_app_cb()`
-+ * (in the daemon process) will understand.
++ * argv[2] contains a simple (1 word) command that `test_app_cb()` (in
++ * the daemon process) will understand.
+ */
+static int client__send_ipc(int argc, const char **argv, const char *path)
+{
@@ t/helper/test-simple-ipc.c (new)
+}
+
+/*
++ * Send an IPC command to an already-running server and ask it to
++ * shutdown. "send quit" is an async request and queues a shutdown
++ * event in the server, so we spin and wait here for it to actually
++ * shutdown to make the unit tests a little easier to write.
++ */
++static int client__stop_server(int argc, const char **argv, const char *path)
++{
++ const char *send_quit[] = { argv[0], "send", "quit", NULL };
++ int max_wait_sec = 60;
++ int ret;
++ time_t time_limit, now;
++ enum ipc_active_state s;
++
++ const char * const stop_usage[] = {
++ N_("test-helper simple-ipc stop-daemon [<options>]"),
++ NULL
++ };
++
++ struct option stop_options[] = {
++ OPT_INTEGER(0, "max-wait", &max_wait_sec,
++ N_("seconds to wait for daemon to stop")),
++ OPT_END()
++ };
++
++ argc = parse_options(argc, argv, NULL, stop_options, stop_usage, 0);
++
++ if (max_wait_sec < 0)
++ max_wait_sec = 0;
++
++ time(&time_limit);
++ time_limit += max_wait_sec;
++
++ ret = client__send_ipc(3, send_quit, path);
++ if (ret)
++ return ret;
++
++ for (;;) {
++ sleep_millisec(100);
++
++ s = ipc_get_active_state(path);
++
++ if (s != IPC_STATE__LISTENING) {
++ /*
++ * The socket/pipe is gone and/or has stopped
++ * responding. Lets assume that the daemon
++ * process has exited too.
++ */
++ return 0;
++ }
++
++ time(&now);
++ if (now > time_limit)
++ return error(_("daemon has not shutdown yet"));
++ }
++}
++
++/*
+ * Send an IPC command followed by ballast to confirm that a large
+ * message can be sent and that the kernel or pkt-line layers will
+ * properly chunk it and that the daemon receives the entire message.
@@ t/helper/test-simple-ipc.c (new)
+ if (client__probe_server(path))
+ return 1;
+
++ if (argc >= 2 && !strcmp(argv[1], "stop-daemon"))
++ return !!client__stop_server(argc, argv, path);
++
+ if ((argc == 2 || argc == 3) && !strcmp(argv[1], "send"))
+ return !!client__send_ipc(argc, argv, path);
+
@@ t/t0052-simple-ipc.sh (new)
+}
+
+stop_simple_IPC_server () {
-+ test-tool simple-ipc send quit
++ test-tool simple-ipc stop-daemon
+}
+
+test_expect_success 'start simple command server' '
@@ t/t0052-simple-ipc.sh (new)
+ test_cmp expect_a actual_a
+'
+
-+# Sending a "quit" message to the server causes it to start an "async
-+# shutdown" -- queuing shutdown events to all socket/pipe thread-pool
-+# threads. Each thread will process that event after finishing
-+# (draining) any in-progress IO with other clients. So when the "send
-+# quit" client command exits, the ipc-server may still be running (but
-+# it should be cleaning up).
-+#
-+# So, insert a generous sleep here to give the server time to shutdown.
-+#
-+test_expect_success '`quit` works' '
-+ test-tool simple-ipc send quit &&
-+
-+ sleep 5 &&
-+
++test_expect_success 'stop-daemon works' '
++ test-tool simple-ipc stop-daemon &&
+ test_must_fail test-tool simple-ipc is-active &&
+ test_must_fail test-tool simple-ipc send ping
+'Signature · 1 line
gitgitgadget
PATCH v4 1/12 pkt-line: eliminate the need for static buffer in packet_write_gently()
From: Jeff Hostetler <[email protected]>
Teach packet_write_gently() to write the pkt-line header and the actual buffer in 2 separate calls to write_in_full() and avoid the need for a static buffer, thread-safe scratch space, or an excessively large stack buffer.
Change the API of write_packetized_from_fd() to accept a scratch space argument from its caller to avoid similar issues here.
These changes are intended to make it easier to use pkt-line routines in a multi-threaded context with multiple concurrent writers writing to different streams.
--- convert.c | 7 ++++--- pkt-line.c | 28 +++++++++++++++++++--------- pkt-line.h | 12 +++++++++--- 3 files changed, 32 insertions(+), 15 deletions(-)
convert.c +4 -3
@@ -883,9 +883,10 @@ static int apply_multi_file_filter(const char *path, const char *src, size_t lenif (err) goto done; -if (fd >=0 ) - err =write_packetized_from_fd (fd, process->in); -else +if (fd >=0 ) { +struct packet_scratch_space scratch; + err =write_packetized_from_fd (fd, process->in, &scratch); + }else err =write_packetized_from_buf (src, len, process->in);if (err) goto done;
pkt-line.c +19 -9
@@ -196,17 +196,25 @@ int packet_write_fmt_gently(int fd, const char *fmt, ...)static int packet_write_gently (const int fd_out,const char *buf,size_t size) { -static char packet_write_buffer[LARGE_PACKET_MAX ]; +char header[4 ];size_t packet_size; -if (size >sizeof (packet_write_buffer) -4 ) +if (size >LARGE_PACKET_DATA_MAX )return error (_ ("packet write failed - data exceeds max packet size" ));packet_trace (buf, size,1 ); packet_size = size +4 ; -set_packet_header (packet_write_buffer, packet_size); -memcpy (packet_write_buffer +4 , buf, size); -if (write_in_full (fd_out, packet_write_buffer, packet_size) <0 ) + +set_packet_header (header, packet_size); + +/* +* Write the header and the buffer in 2 parts so that we do not need +* to allocate a buffer or rely on a static buffer. This avoids perf +* and multi-threading issues. +*/ + +if (write_in_full (fd_out, header,4 ) <0 || +write_in_full (fd_out, buf, size) <0 )return error (_ ("packet write failed" ));return 0 ; }
@@ -242,19 +250,21 @@ void packet_buf_write_len(struct strbuf *buf, const char *data, size_t len)packet_trace (data, len,1 ); } -int write_packetized_from_fd (int fd_in,int fd_out) +int write_packetized_from_fd (int fd_in,int fd_out, +struct packet_scratch_space *scratch) { -static char buf[LARGE_PACKET_DATA_MAX ];int err =0 ;ssize_t bytes_to_write;while (!err) { - bytes_to_write =xread (fd_in, buf,sizeof (buf)); + bytes_to_write =xread (fd_in, scratch->buffer, +sizeof (scratch->buffer));if (bytes_to_write <0 )return COPY_READ_ERROR ;if (bytes_to_write ==0 )break ; - err =packet_write_gently (fd_out, buf, bytes_to_write); + err =packet_write_gently (fd_out, scratch->buffer, + bytes_to_write); }if (!err) err =packet_flush_gently (fd_out);
pkt-line.h +9 -3
@@ -5,6 +5,13 @@#include "strbuf.h" #include "sideband.h" +#define LARGE_PACKET_MAX 65520 +#define LARGE_PACKET_DATA_MAX (LARGE_PACKET_MAX - 4) + +struct packet_scratch_space { +char buffer[LARGE_PACKET_DATA_MAX ];/* does not include header bytes */ +}; +/* * Write a packetized stream, where each line is preceded by * its length (including the header) as a 4-byte hex number.
@@ -32,7 +39,7 @@ void packet_buf_write(struct strbuf *buf, const char *fmt, ...) __attribute__((fvoid packet_buf_write_len (struct strbuf *buf,const char *data,size_t len);int packet_flush_gently (int fd);int packet_write_fmt_gently (int fd,const char *fmt, ...) __attribute__((format (printf,2 ,3 ))); -int write_packetized_from_fd (int fd_in,int fd_out); +int write_packetized_from_fd (int fd_in,int fd_out,struct packet_scratch_space *scratch);int write_packetized_from_buf (const char *src_in,size_t len,int fd_out);/*
@@ -213,8 +220,7 @@ enum packet_read_status packet_reader_read(struct packet_reader *reader);enum packet_read_status packet_reader_peek (struct packet_reader *reader);#define DEFAULT_PACKET_MAX 1000 -#define LARGE_PACKET_MAX 65520 -#define LARGE_PACKET_DATA_MAX (LARGE_PACKET_MAX - 4) +extern char packet_buffer[LARGE_PACKET_MAX ];struct packet_writer {
Signature · 1 line
gitgitgadget
PATCH v4 2/12 pkt-line: do not issue flush packets in write_packetized_*()
In reply to Jeff Hostetler via GitGitGadgetFrom: Johannes Schindelin <[email protected]>
Remove the packet_flush_gently() call in write_packetized_from_buf() and write_packetized_from_fd() and require the caller to call it if desired. Rename both functions to write_packetized_from_*_no_flush()` to prevent later merge accidents.
write_packetized_from_buf() currently only has one caller:apply_multi_file_filter() in convert.c. It always wants a flush packet to be written after writing the payload.
However, we are about to introduce a caller that wants to write many packets before a final flush packet, so let's make the caller responsible for emitting the flush packet.
--- convert.c | 8 ++++++-- pkt-line.c | 10 +++------- pkt-line.h | 4 ++-- 3 files changed, 11 insertions(+), 11 deletions(-)
convert.c +6 -2
@@ -885,9 +885,13 @@ static int apply_multi_file_filter(const char *path, const char *src, size_t lenif (fd >=0 ) {struct packet_scratch_space scratch; - err =write_packetized_from_fd (fd, process->in, &scratch); + err =write_packetized_from_fd_no_flush (fd, process->in, &scratch); }else - err =write_packetized_from_buf (src, len, process->in); + err =write_packetized_from_buf_no_flush (src, len, process->in); +if (err) + goto done; + + err =packet_flush_gently (process->in);if (err) goto done;
pkt-line.c +3 -7
@@ -250,8 +250,8 @@ void packet_buf_write_len(struct strbuf *buf, const char *data, size_t len)packet_trace (data, len,1 ); } -int write_packetized_from_fd (int fd_in,int fd_out, -struct packet_scratch_space *scratch) +int write_packetized_from_fd_no_flush (int fd_in,int fd_out, +struct packet_scratch_space *scratch) {int err =0 ;ssize_t bytes_to_write;
@@ -266,12 +266,10 @@ int write_packetized_from_fd(int fd_in, int fd_out, err =packet_write_gently (fd_out, scratch->buffer, bytes_to_write); } -if (!err) - err =packet_flush_gently (fd_out);return err; } -int write_packetized_from_buf (const char *src_in,size_t len,int fd_out) +int write_packetized_from_buf_no_flush (const char *src_in,size_t len,int fd_out) {int err =0 ;size_t bytes_written =0 ;
@@ -287,8 +285,6 @@ int write_packetized_from_buf(const char *src_in, size_t len, int fd_out) err =packet_write_gently (fd_out, src_in + bytes_written, bytes_to_write); bytes_written += bytes_to_write; } -if (!err) - err =packet_flush_gently (fd_out);return err; }
pkt-line.h +2 -2
@@ -39,8 +39,8 @@ void packet_buf_write(struct strbuf *buf, const char *fmt, ...) __attribute__((fvoid packet_buf_write_len (struct strbuf *buf,const char *data,size_t len);int packet_flush_gently (int fd);int packet_write_fmt_gently (int fd,const char *fmt, ...) __attribute__((format (printf,2 ,3 ))); -int write_packetized_from_fd (int fd_in,int fd_out,struct packet_scratch_space *scratch); -int write_packetized_from_buf (const char *src_in,size_t len,int fd_out); +int write_packetized_from_fd_no_flush (int fd_in,int fd_out,struct packet_scratch_space *scratch); +int write_packetized_from_buf_no_flush (const char *src_in,size_t len,int fd_out);/* * Read a packetized line into the buffer, which must be at least size bytes
Signature · 1 line
gitgitgadget
PATCH v4 3/12 pkt-line: (optionally) libify the packet readers
In reply to Jeff Hostetler via GitGitGadgetFrom: Johannes Schindelin <[email protected]>
So far, the (possibly indirect) callers of get_packet_data() can ask that function to return an error instead of die()ing upon end-of-file. However, random read errors will still cause the process to die.
So let's introduce an explicit option to tell the packet reader machinery to please be nice and only return an error.
This change prepares pkt-line for use by long-running daemon processes. Such processes should be able to serve multiple concurrent clients and and survive random IO errors. If there is an error on one connection, a daemon should be able to drop that connection and continue serving existing and future connections.
This ability will be used by a Git-aware "Internal FSMonitor" feature in a later patch series.
--- pkt-line.c | 19 +++++++++++++++++-- pkt-line.h | 4 ++++ 2 files changed, 21 insertions(+), 2 deletions(-)
pkt-line.c +17 -2
@@ -304,8 +304,11 @@ static int get_packet_data(int fd, char **src_buf, size_t *src_size, *src_size -= ret; }else { ret =read_in_full (fd, dst, size); -if (ret <0 ) +if (ret <0 ) { +if (options &PACKET_READ_NEVER_DIE ) +return error_errno (_ ("read error" ));die_errno (_ ("read error" )); + } }/* And complain if we didn't get enough bytes to satisfy the read. */
@@ -313,6 +316,8 @@ static int get_packet_data(int fd, char **src_buf, size_t *src_size,if (options &PACKET_READ_GENTLE_ON_EOF )return -1 ; +if (options &PACKET_READ_NEVER_DIE ) +return error (_ ("the remote end hung up unexpectedly" ));die (_ ("the remote end hung up unexpectedly" )); }
@@ -341,6 +346,9 @@ enum packet_read_status packet_read_with_status(int fd, char **src_buffer, len =packet_length (linelen);if (len <0 ) { +if (options &PACKET_READ_NEVER_DIE ) +return error (_ ("protocol error: bad line length " +"character: %.4s" ), linelen);die (_ ("protocol error: bad line length character: %.4s" ), linelen); }else if (!len) {packet_trace ("0000" ,4 ,0 );
@@ -355,12 +363,19 @@ enum packet_read_status packet_read_with_status(int fd, char **src_buffer, *pktlen =0 ;return PACKET_READ_RESPONSE_END ; }else if (len <4 ) { +if (options &PACKET_READ_NEVER_DIE ) +return error (_ ("protocol error: bad line length %d" ), + len);die (_ ("protocol error: bad line length %d" ), len); } len -=4 ; -if ((unsigned )len >= size) +if ((unsigned )len >= size) { +if (options &PACKET_READ_NEVER_DIE ) +return error (_ ("protocol error: bad line length %d" ), + len);die (_ ("protocol error: bad line length %d" ), len); + }if (get_packet_data (fd, src_buffer, src_len, buffer, len, options) <0 ) { *pktlen =-1 ;
pkt-line.h +4 -0
@@ -75,10 +75,14 @@ int write_packetized_from_buf_no_flush(const char *src_in, size_t len, int fd_ou* * If options contains PACKET_READ_DIE_ON_ERR_PACKET, it dies when it sees an * ERR packet. +* +* With `PACKET_READ_NEVER_DIE`, no errors are allowed to trigger die() (except +* an ERR packet, when `PACKET_READ_DIE_ON_ERR_PACKET` is in effect). */ #define PACKET_READ_GENTLE_ON_EOF (1u<<0)#define PACKET_READ_CHOMP_NEWLINE (1u<<1)#define PACKET_READ_DIE_ON_ERR_PACKET (1u<<2) +#define PACKET_READ_NEVER_DIE (1u<<3)int packet_read (int fd,char **src_buffer,size_t *src_len,char *buffer,unsigned size,int options);
Signature · 1 line
gitgitgadget
PATCH v4 4/12 pkt-line: add options argument to read_packetized_to_strbuf()
In reply to Jeff Hostetler via GitGitGadgetFrom: Johannes Schindelin <[email protected]>
Update the calling sequence of read_packetized_to_strbuf() to take an options argument and not assume a fixed set of options. Update the only existing caller accordingly to explicitly pass the formerly-assumed flags.
The read_packetized_to_strbuf() function calls packet_read() with a fixed set of assumed options (PACKET_READ_GENTLE_ON_EOF). This assumption has been fine for the single existing caller apply_multi_file_filter() in convert.c.
In a later commit we would like to add other callers to read_packetized_to_strbuf() that need a different set of options.
--- convert.c | 3 ++- pkt-line.c | 4 ++-- pkt-line.h | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-)
convert.c +2 -1
@@ -908,7 +908,8 @@ static int apply_multi_file_filter(const char *path, const char *src, size_t lenif (err) goto done; - err =read_packetized_to_strbuf (process->out, &nbuf) <0 ; + err =read_packetized_to_strbuf (process->out, &nbuf, +PACKET_READ_GENTLE_ON_EOF ) <0 ;if (err) goto done;
pkt-line.c +2 -2
@@ -442,7 +442,7 @@ char *packet_read_line_buf(char **src, size_t *src_len, int *dst_len)return packet_read_line_generic (-1 , src, src_len, dst_len); } -ssize_t read_packetized_to_strbuf (int fd_in,struct strbuf *sb_out) +ssize_t read_packetized_to_strbuf (int fd_in,struct strbuf *sb_out,int options) {int packet_len;
@@ -458,7 +458,7 @@ ssize_t read_packetized_to_strbuf(int fd_in, struct strbuf *sb_out)* that there is already room for the extra byte. */ sb_out->buf + sb_out->len,LARGE_PACKET_DATA_MAX +1 , - PACKET_READ_GENTLE_ON_EOF); + options);if (packet_len <=0 )break ; sb_out->len += packet_len;
pkt-line.h +1 -1
@@ -142,7 +142,7 @@ char *packet_read_line_buf(char **src_buf, size_t *src_len, int *size);/* * Reads a stream of variable sized packets until a flush packet is detected. */ -ssize_t read_packetized_to_strbuf (int fd_in,struct strbuf *sb_out); +ssize_t read_packetized_to_strbuf (int fd_in,struct strbuf *sb_out,int options);/* * Receive multiplexed output stream over git native protocol.
Signature · 1 line
gitgitgadget
PATCH v4 5/12 simple-ipc: design documentation for new IPC mechanism
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Brief design documentation for new IPC mechanism allowing foreground Git client to talk with an existing daemon process at a known location using a named pipe or unix domain socket.
--- Documentation/technical/api-simple-ipc.txt | 34 ++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 Documentation/technical/api-simple-ipc.txt
Documentation/technical/api-simple-ipc.txt new file +34 -0
@@ -0,0 +1,34 @@ +simple-ipc API +============== + +The simple-ipc API is used to send an IPC message and response between +a (presumably) foreground Git client process to a background server or +daemon process. The server process must already be running. Multiple +client processes can simultaneously communicate with the server +process. + +Communication occurs over a named pipe on Windows and a Unix domain +socket on other platforms. Clients and the server rendezvous at a +previously agreed-to application-specific pathname (which is outside +the scope of this design). + +This IPC mechanism differs from the existing `sub-process.c` model +(Documentation/technical/long-running-process-protocol.txt) and used +by applications like Git-LFS. In the simple-ipc model the server is +assumed to be a very long-running system service. In contrast, in the +LFS-style sub-process model the helper is started with the foreground +process and exits when the foreground process terminates. + +How the simple-ipc server is started is also outside the scope of the +IPC mechanism. For example, the server might be started during +maintenance operations. + +The IPC protocol consists of a single request message from the client and +an optional request message from the server. For simplicity, pkt-line +routines are used to hide chunking and buffering concerns. Each side +terminates their message with a flush packet. +(Documentation/technical/protocol-common.txt) + +The actual format of the client and server messages is application +specific. The IPC layer transmits and receives an opaque buffer without +any concern for the content within.
Signature · 1 line
gitgitgadget
PATCH v4 6/12 simple-ipc: add win32 implementation
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Create Windows implementation of "simple-ipc" using named pipes.
--- Makefile | 5 + compat/simple-ipc/ipc-shared.c | 28 ++ compat/simple-ipc/ipc-win32.c | 749 ++++++++++++++++++++++++++++ config.mak.uname | 2 + contrib/buildsystems/CMakeLists.txt | 4 + simple-ipc.h | 224 +++++++++ 6 files changed, 1012 insertions(+) create mode 100644 compat/simple-ipc/ipc-shared.c create mode 100644 compat/simple-ipc/ipc-win32.c create mode 100644 simple-ipc.h
Makefile +5 -0
@@ -1679,6 +1679,11 @@ else LIB_OBJS += unix-socket.o endif +ifdef USE_WIN32_IPC + LIB_OBJS += compat/simple-ipc/ipc-shared.o + LIB_OBJS += compat/simple-ipc/ipc-win32.o +endif + ifdef NO_ICONV BASIC_CFLAGS += -DNO_ICONV endif
compat/simple-ipc/ipc-shared.c new file +28 -0
@@ -0,0 +1,28 @@ +#include "cache.h" +#include "simple-ipc.h" +#include "strbuf.h" +#include "pkt-line.h" +#include "thread-utils.h" + +#ifdef SUPPORTS_SIMPLE_IPC + +int ipc_server_run (const char *path,const struct ipc_server_opts *opts, +ipc_server_application_cb *application_cb, +void *application_data) +{ +struct ipc_server_data *server_data =NULL ; +int ret; + + ret =ipc_server_run_async (&server_data, path, opts, + application_cb, application_data); +if (ret) +return ret; + + ret =ipc_server_await (server_data); + +ipc_server_free (server_data); + +return ret; +} + +#endif /* SUPPORTS_SIMPLE_IPC */
compat/simple-ipc/ipc-win32.c new file +749 -0
749 lines not on this page · raw message
config.mak.uname +2 -0
@@ -421,6 +421,7 @@ ifeq ($(uname_S),Windows) RUNTIME_PREFIX = YesPlease HAVE_WPGMPTR = YesWeDo NO_ST_BLOCKS_IN_STRUCT_STAT = YesPlease + USE_WIN32_IPC = YesPlease USE_WIN32_MMAP = YesPlease MMAP_PREVENTS_DELETE = UnfortunatelyYes # USE_NED_ALLOCATOR = YesPlease
@@ -597,6 +598,7 @@ ifneq (,$(findstring MINGW,$(uname_S))) RUNTIME_PREFIX = YesPlease HAVE_WPGMPTR = YesWeDo NO_ST_BLOCKS_IN_STRUCT_STAT = YesPlease + USE_WIN32_IPC = YesPlease USE_WIN32_MMAP = YesPlease MMAP_PREVENTS_DELETE = UnfortunatelyYes USE_NED_ALLOCATOR = YesPlease
contrib/buildsystems/CMakeLists.txt +4 -0
@@ -246,6 +246,10 @@ elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux") list(APPEND compat_SOURCES unix-socket.c) endif() +if(CMAKE_SYSTEM_NAME STREQUAL "Windows") + list(APPEND compat_SOURCES compat/simple-ipc/ipc-shared.c compat/simple-ipc/ipc-win32.c) +endif() + set(EXE_EXTENSION ${CMAKE_EXECUTABLE_SUFFIX}) #header checks
simple-ipc.h new file +224 -0
224 lines not on this page · raw message
Signature · 1 line
gitgitgadget
PATCH v4 7/12 unix-socket: elimiate static unix_stream_socket() helper function
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
The static helper function unix_stream_socket() calls die(). This is not appropriate for all callers. Eliminate the wrapper function and make the callers propagate the error.
--- unix-socket.c | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-)
unix-socket.c +13 -14
@@ -1,14 +1,6 @@#include "cache.h" #include "unix-socket.h" -static int unix_stream_socket (void ) -{ -int fd =socket (AF_UNIX ,SOCK_STREAM ,0 ); -if (fd <0 ) -die_errno ("unable to create socket" ); -return fd; -} -static int chdir_len (const char *orig,int len) {char *path =xmemdupz (orig, len);
@@ -73,13 +65,16 @@ static int unix_sockaddr_init(struct sockaddr_un *sa, const char *path,int unix_stream_connect (const char *path) { -int fd, saved_errno; +int fd =-1 , saved_errno;struct sockaddr_un sa;struct unix_sockaddr_context ctx;if (unix_sockaddr_init (&sa, path, &ctx) <0 )return -1 ; - fd =unix_stream_socket (); + fd =socket (AF_UNIX ,SOCK_STREAM ,0 ); +if (fd <0 ) + goto fail; +if (connect (fd, (struct sockaddr *)&sa,sizeof (sa)) <0 ) goto fail;unix_sockaddr_cleanup (&ctx);
@@ -87,15 +82,16 @@ int unix_stream_connect(const char *path) fail: saved_errno = errno; +if (fd !=-1 ) +close (fd);unix_sockaddr_cleanup (&ctx); -close (fd); errno = saved_errno;return -1 ; }int unix_stream_listen (const char *path) { -int fd, saved_errno; +int fd =-1 , saved_errno;struct sockaddr_un sa;struct unix_sockaddr_context ctx;
@@ -103,7 +99,9 @@ int unix_stream_listen(const char *path)if (unix_sockaddr_init (&sa, path, &ctx) <0 )return -1 ; - fd =unix_stream_socket (); + fd =socket (AF_UNIX ,SOCK_STREAM ,0 ); +if (fd <0 ) + goto fail;if (bind (fd, (struct sockaddr *)&sa,sizeof (sa)) <0 ) goto fail;
@@ -116,8 +114,9 @@ int unix_stream_listen(const char *path) fail: saved_errno = errno; +if (fd !=-1 ) +close (fd);unix_sockaddr_cleanup (&ctx); -close (fd); errno = saved_errno;return -1 ; }
Signature · 1 line
gitgitgadget
PATCH v4 8/12 unix-socket: add backlog size option to unix_stream_listen()
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Update unix_stream_listen() to take an options structure to override default behaviors. This commit includes the size of the listen() backlog.
--- builtin/credential-cache--daemon.c | 3 ++- unix-socket.c | 9 +++++++-- unix-socket.h | 14 +++++++++++++- 3 files changed, 22 insertions(+), 4 deletions(-)
builtin/credential-cache--daemon.c +2 -1
@@ -203,9 +203,10 @@ static int serve_cache_loop(int fd)static void serve_cache (const char *socket_path,int debug) { +struct unix_stream_listen_opts opts =UNIX_STREAM_LISTEN_OPTS_INIT ;int fd; - fd =unix_stream_listen (socket_path); + fd =unix_stream_listen (socket_path, &opts);if (fd <0 )die_errno ("unable to bind to '%s'" , socket_path);
unix-socket.c +7 -2
@@ -89,9 +89,11 @@ int unix_stream_connect(const char *path)return -1 ; } -int unix_stream_listen (const char *path) +int unix_stream_listen (const char *path, +const struct unix_stream_listen_opts *opts) {int fd =-1 , saved_errno; +int backlog;struct sockaddr_un sa;struct unix_sockaddr_context ctx;
@@ -106,7 +108,10 @@ int unix_stream_listen(const char *path)if (bind (fd, (struct sockaddr *)&sa,sizeof (sa)) <0 ) goto fail; -if (listen (fd,5 ) <0 ) + backlog = opts->listen_backlog_size; +if (backlog <=0 ) + backlog =DEFAULT_UNIX_STREAM_LISTEN_BACKLOG ; +if (listen (fd, backlog) <0 ) goto fail;unix_sockaddr_cleanup (&ctx);
unix-socket.h +13 -1
@@ -1,7 +1,19 @@#ifndef UNIX_SOCKET_H #define UNIX_SOCKET_H +struct unix_stream_listen_opts { +int listen_backlog_size; +}; + +#define DEFAULT_UNIX_STREAM_LISTEN_BACKLOG (5) + +#define UNIX_STREAM_LISTEN_OPTS_INIT \ +{ \ + .listen_backlog_size = DEFAULT_UNIX_STREAM_LISTEN_BACKLOG, \ +} +int unix_stream_connect (const char *path); -int unix_stream_listen (const char *path); +int unix_stream_listen (const char *path, +const struct unix_stream_listen_opts *opts);#endif /* UNIX_SOCKET_H */
Signature · 1 line
gitgitgadget
PATCH v4 9/12 unix-socket: disallow chdir() when creating unix domain sockets
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Calls to chdir() are dangerous in a multi-threaded context. If unix_stream_listen() or unix_stream_connect() is given a socket pathname that is too long to fit in a sockaddr_un structure, it will chdir() to the parent directory of the requested socket pathname, create the socket using a relative pathname, and then chdir() back. This is not thread-safe.
Teach unix_sockaddr_init() to not allow calls to chdir() when this flag is set.
--- builtin/credential-cache.c | 2 +- unix-socket.c | 17 ++++++++++++----- unix-socket.h | 4 +++- 3 files changed, 16 insertions(+), 7 deletions(-)
builtin/credential-cache.c +1 -1
@@ -14,7 +14,7 @@static int send_request (const char *socket,const struct strbuf *out) {int got_data =0 ; -int fd =unix_stream_connect (socket); +int fd =unix_stream_connect (socket,0 );if (fd <0 )return -1 ;
unix-socket.c +12 -5
@@ -28,16 +28,23 @@ static void unix_sockaddr_cleanup(struct unix_sockaddr_context *ctx) }static int unix_sockaddr_init (struct sockaddr_un *sa,const char *path, -struct unix_sockaddr_context *ctx) +struct unix_sockaddr_context *ctx, +int disallow_chdir) {int size =strlen (path) +1 ; ctx->orig_dir =NULL ;if (size >sizeof (sa->sun_path)) { -const char *slash =find_last_dir_sep (path); +const char *slash;const char *dir;struct strbuf cwd =STRBUF_INIT ; +if (disallow_chdir) { + errno =ENAMETOOLONG ; +return -1 ; + } + + slash =find_last_dir_sep (path);if (!slash) { errno =ENAMETOOLONG ;return -1 ;
@@ -63,13 +70,13 @@ static int unix_sockaddr_init(struct sockaddr_un *sa, const char *path,return 0 ; } -int unix_stream_connect (const char *path) +int unix_stream_connect (const char *path,int disallow_chdir) {int fd =-1 , saved_errno;struct sockaddr_un sa;struct unix_sockaddr_context ctx; -if (unix_sockaddr_init (&sa, path, &ctx) <0 ) +if (unix_sockaddr_init (&sa, path, &ctx, disallow_chdir) <0 )return -1 ; fd =socket (AF_UNIX ,SOCK_STREAM ,0 );if (fd <0 )
@@ -99,7 +106,7 @@ int unix_stream_listen(const char *path,unlink (path); -if (unix_sockaddr_init (&sa, path, &ctx) <0 ) +if (unix_sockaddr_init (&sa, path, &ctx, opts->disallow_chdir) <0 )return -1 ; fd =socket (AF_UNIX ,SOCK_STREAM ,0 );if (fd <0 )
unix-socket.h +3 -1
@@ -3,6 +3,7 @@struct unix_stream_listen_opts {int listen_backlog_size; +unsigned int disallow_chdir:1 ; };#define DEFAULT_UNIX_STREAM_LISTEN_BACKLOG (5)
@@ -10,9 +11,10 @@ struct unix_stream_listen_opts {#define UNIX_STREAM_LISTEN_OPTS_INIT \ { \ .listen_backlog_size = DEFAULT_UNIX_STREAM_LISTEN_BACKLOG, \ + .disallow_chdir = 0, \ } -int unix_stream_connect (const char *path); +int unix_stream_connect (const char *path,int disallow_chdir);int unix_stream_listen (const char *path,const struct unix_stream_listen_opts *opts);
Signature · 1 line
gitgitgadget
PATCH v4 10/12 unix-socket: create `unix_stream_server__listen_with_lock()`
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Create a version of unix_stream_listen() that uses a ".lock" lockfile to create the unix domain socket in a race-free manner.
Unix domain sockets have a fundamental problem on Unix systems because they persist in the filesystem until they are deleted. This is independent of whether a server is actually listening for connections. Well-behaved servers are expected to delete the socket when they shutdown. A new server cannot easily tell if a found socket is attached to an active server or is leftover cruft from a dead server. The traditional solution used by unix_stream_listen() is to force delete the socket pathname and then create a new socket. This solves the latter (cruft) problem, but in the case of the former, it orphans the existing server (by stealing the pathname associated with the socket it is listening on).
We cannot directly use a .lock lockfile to create the socket because the socket is created by bind(2) rather than the open(2) mechanism used by tempfile.c.
As an alternative, we hold a plain lockfile ("<path>.lock") as a mutual exclusion device. Under the lock, we test if an existing socket ("<path>") is has an active server. If not, create a new socket and begin listening. Then we rollback the lockfile in all cases.
--- unix-socket.c | 115 ++++++++++++++++++++++++++++++++++++++++++++++++++ unix-socket.h | 29 +++++++++++++ 2 files changed, 144 insertions(+)
unix-socket.c +115 -0
@@ -1,4 +1,5 @@#include "cache.h" +#include "lockfile.h" #include "unix-socket.h" static int chdir_len (const char *orig,int len)
@@ -132,3 +133,117 @@ int unix_stream_listen(const char *path, errno = saved_errno;return -1 ; } + +static int is_another_server_alive (const char *path, +const struct unix_stream_listen_opts *opts) +{ +struct stat st; +int fd; + +if (!lstat (path, &st) &&S_ISSOCK (st.st_mode)) { +/* +* A socket-inode exists on disk at `path`, but we +* don't know whether it belongs to an active server +* or whether the last server died without cleaning +* up. +* +* Poke it with a trivial connection to try to find +* out. +*/ + fd =unix_stream_connect (path, opts->disallow_chdir); +if (fd >=0 ) { +close (fd); +return 1 ; + } + } + +return 0 ; +} + +struct unix_stream_server_socket *unix_stream_server__listen_with_lock ( +const char *path, +const struct unix_stream_listen_opts *opts) +{ +struct lock_file lock =LOCK_INIT ; +int fd_socket; +struct unix_stream_server_socket *server_socket; + +/* +* Create a lock at "<path>.lock" if we can. +*/ +if (hold_lock_file_for_update_timeout (&lock, path,0 , + opts->timeout_ms) <0 ) { +error_errno (_ ("could not lock listener socket '%s'" ), path); +return NULL ; + } + +/* +* If another server is listening on "<path>" give up. We do not +* want to create a socket and steal future connections from them. +*/ +if (is_another_server_alive (path, opts)) { + errno =EADDRINUSE ; +error_errno (_ ("listener socket already in use '%s'" ), path); +rollback_lock_file (&lock); +return NULL ; + } + +/* +* Create and bind to a Unix domain socket at "<path>". +*/ + fd_socket =unix_stream_listen (path, opts); +if (fd_socket <0 ) { +error_errno (_ ("could not create listener socket '%s'" ), path); +rollback_lock_file (&lock); +return NULL ; + } + + server_socket =xcalloc (1 ,sizeof (*server_socket)); + server_socket->path_socket =strdup (path); + server_socket->fd_socket = fd_socket; +lstat (path, &server_socket->st_socket); + +/* +* Always rollback (just delete) "<path>.lock" because we already created +* "<path>" as a socket and do not want to commit_lock to do the atomic +* rename trick. +*/ +rollback_lock_file (&lock); + +return server_socket; +} + +void unix_stream_server__free ( +struct unix_stream_server_socket *server_socket) +{ +if (!server_socket) +return ; + +if (server_socket->fd_socket >=0 ) { +if (!unix_stream_server__was_stolen (server_socket)) +unlink (server_socket->path_socket); +close (server_socket->fd_socket); + } + +free (server_socket->path_socket); +free (server_socket); +} + +int unix_stream_server__was_stolen ( +struct unix_stream_server_socket *server_socket) +{ +struct stat st_now; + +if (!server_socket) +return 0 ; + +if (lstat (server_socket->path_socket, &st_now) ==-1 ) +return 1 ; + +if (st_now.st_ino != server_socket->st_socket.st_ino) +return 1 ; + +/* We might also consider the ctime on some platforms. */ + +return 0 ; +}
unix-socket.h +29 -0
@@ -2,14 +2,17 @@#define UNIX_SOCKET_H struct unix_stream_listen_opts { +long timeout_ms;int listen_backlog_size;unsigned int disallow_chdir:1 ; }; +#define DEFAULT_UNIX_STREAM_LISTEN_TIMEOUT (100)#define DEFAULT_UNIX_STREAM_LISTEN_BACKLOG (5)#define UNIX_STREAM_LISTEN_OPTS_INIT \ { \ + .timeout_ms = DEFAULT_UNIX_STREAM_LISTEN_TIMEOUT, \ .listen_backlog_size = DEFAULT_UNIX_STREAM_LISTEN_BACKLOG, \ .disallow_chdir = 0, \ }
@@ -18,4 +21,30 @@ int unix_stream_connect(const char *path, int disallow_chdir);int unix_stream_listen (const char *path,const struct unix_stream_listen_opts *opts); +struct unix_stream_server_socket { +char *path_socket; +struct stat st_socket; +int fd_socket; +}; + +/* +* Create a Unix Domain Socket at the given path under the protection +* of a '.lock' lockfile. +*/ +struct unix_stream_server_socket *unix_stream_server__listen_with_lock ( +const char *path, +const struct unix_stream_listen_opts *opts); + +/* +* Close and delete the socket. +*/ +void unix_stream_server__free ( +struct unix_stream_server_socket *server_socket); + +/* +* Return 1 if the inode of the pathname to our socket changes. +*/ +int unix_stream_server__was_stolen ( +struct unix_stream_server_socket *server_socket); +#endif /* UNIX_SOCKET_H */
Signature · 1 line
gitgitgadget
PATCH v4 12/12 t0052: add simple-ipc tests and t/helper/test-simple-ipc tool
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Create t0052-simple-ipc.sh with unit tests for the "simple-ipc" mechanism.
Create t/helper/test-simple-ipc test tool to exercise the "simple-ipc" functions.
When the tool is invoked with "run-daemon", it runs a server to listen for "simple-ipc" connections on a test socket or named pipe and responds to a set of commands to exercise/stress the communication setup.
When the tool is invoked with "start-daemon", it spawns a "run-daemon" command in the background and waits for the server to become ready before exiting. (This helps make unit tests in t0052 more predictable and avoids the need for arbitrary sleeps in the test script.)
The tool also has a series of client "send" commands to send commands and data to a server instance.
--- Makefile | 1 + t/helper/test-simple-ipc.c | 773 +++++++++++++++++++++++++++++++++++++ t/helper/test-tool.c | 1 + t/helper/test-tool.h | 1 + t/t0052-simple-ipc.sh | 122 ++++++ 5 files changed, 898 insertions(+) create mode 100644 t/helper/test-simple-ipc.c create mode 100755 t/t0052-simple-ipc.sh
Makefile +1 -0
@@ -740,6 +740,7 @@ TEST_BUILTINS_OBJS += test-serve-v2.o TEST_BUILTINS_OBJS += test-sha1.o TEST_BUILTINS_OBJS += test-sha256.o TEST_BUILTINS_OBJS += test-sigchain.o +TEST_BUILTINS_OBJS += test-simple-ipc.o TEST_BUILTINS_OBJS += test-strcmp-offset.o TEST_BUILTINS_OBJS += test-string-list.o TEST_BUILTINS_OBJS += test-submodule-config.o
t/helper/test-simple-ipc.c new file +773 -0
773 lines not on this page · raw message
t/helper/test-tool.c +1 -0
@@ -64,6 +64,7 @@ static struct test_cmd cmds[] = { { "sha1", cmd__sha1 }, { "sha256", cmd__sha256 }, { "sigchain", cmd__sigchain }, + { "simple-ipc", cmd__simple_ipc }, { "strcmp-offset", cmd__strcmp_offset }, { "string-list", cmd__string_list }, { "submodule-config", cmd__submodule_config },
t/helper/test-tool.h +1 -0
@@ -54,6 +54,7 @@ int cmd__sha1(int argc, const char **argv);int cmd__oid_array (int argc,const char **argv);int cmd__sha256 (int argc,const char **argv);int cmd__sigchain (int argc,const char **argv); +int cmd__simple_ipc (int argc,const char **argv);int cmd__strcmp_offset (int argc,const char **argv);int cmd__string_list (int argc,const char **argv);int cmd__submodule_config (int argc,const char **argv);
t/t0052-simple-ipc.sh new file +122 -0
122 lines not on this page · raw message
Signature · 1 line
gitgitgadget
PATCH v4 11/12 simple-ipc: add Unix domain socket implementation
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Create Unix domain socket based implementation of "simple-ipc".
A set of ipc_client routines implement a client library to connect to an ipc_server over a Unix domain socket, send a simple request, and receive a single response. Clients use blocking IO on the socket.
A set of ipc_server routines implement a thread pool to listen for and concurrently service client connections.
The server creates a new Unix domain socket at a known location. If a socket already exists with that name, the server tries to determine if another server is already listening on the socket or if the socket is dead. If socket is busy, the server exits with an error rather than stealing the socket. If the socket is dead, the server creates a new one and starts up.
If while running, the server detects that its socket has been stolen by another server, it automatically exits.
--- Makefile | 2 + compat/simple-ipc/ipc-unix-socket.c | 979 ++++++++++++++++++++++++++++ contrib/buildsystems/CMakeLists.txt | 2 + simple-ipc.h | 13 +- 4 files changed, 995 insertions(+), 1 deletion(-) create mode 100644 compat/simple-ipc/ipc-unix-socket.c
Makefile +2 -0
@@ -1677,6 +1677,8 @@ ifdef NO_UNIX_SOCKETS BASIC_CFLAGS += -DNO_UNIX_SOCKETS else LIB_OBJS += unix-socket.o + LIB_OBJS += compat/simple-ipc/ipc-shared.o + LIB_OBJS += compat/simple-ipc/ipc-unix-socket.o endif ifdef USE_WIN32_IPC
compat/simple-ipc/ipc-unix-socket.c new file +979 -0
979 lines not on this page · raw message
contrib/buildsystems/CMakeLists.txt +2 -0
@@ -248,6 +248,8 @@ endif() if(CMAKE_SYSTEM_NAME STREQUAL "Windows") list(APPEND compat_SOURCES compat/simple-ipc/ipc-shared.c compat/simple-ipc/ipc-win32.c) +else() + list(APPEND compat_SOURCES compat/simple-ipc/ipc-shared.c compat/simple-ipc/ipc-unix-socket.c) endif() set(EXE_EXTENSION ${CMAKE_EXECUTABLE_SUFFIX})
simple-ipc.h +12 -1
@@ -5,7 +5,7 @@* See Documentation/technical/api-simple-ipc.txt */ -#if defined(GIT_WINDOWS_NATIVE ) +#if defined(GIT_WINDOWS_NATIVE ) || !defined(NO_UNIX_SOCKETS )#define SUPPORTS_SIMPLE_IPC #endif
@@ -62,11 +62,17 @@ struct ipc_client_connect_options {* the service and need to wait for it to become ready. */ unsigned int wait_if_not_found:1; + +/* +* Disallow chdir() when creating a Unix domain socket. +*/ + unsigned int uds_disallow_chdir:1; };#define IPC_CLIENT_CONNECT_OPTIONS_INIT { \ .wait_if_busy = 0, \ .wait_if_not_found = 0, \ + .uds_disallow_chdir = 0, \ }/*
@@ -159,6 +165,11 @@ struct ipc_server_data;struct ipc_server_opts {int nr_threads; + +/* +* Disallow chdir() when creating a Unix domain socket. +*/ +unsigned int uds_disallow_chdir:1 ; };/*
Signature · 1 line
gitgitgadget
Quoted 9 lines · Jeff Hostetler via GitGitGadget
Here is V4 of my "Simple IPC" series. It addresses Gábor's comment WRT shutting down the server to make unit tests more predictable on CI servers.
(https://lore.kernel.org/git/[email protected])Jeff
cc: Ævar Arnfjörð Bjarmason [email protected] cc: Jeff Hostetler [email protected] cc: Jeff King [email protected] cc: Chris Torek [email protected]
It seems that the discussions around the topic has mostly done during the v2 review, and has quieted down since then.
Let's merge it down to 'next'?
Re: [PATCH v4 01/12] pkt-line: eliminate the need for static buffer in packet_write_gently()
In reply to Jeff Hostetler via GitGitGadgetQuoted 2 lines · Jeff Hostetler via GitGitGadget
Change the API of
write_packetized_from_fd()to accept a scratch space argument from its caller to avoid similar issues here.
OK, but...
Quoted 6 lines
convert.c +0
-0@@ -883,9 +883,10 @@ static int apply_multi_file_filter(const char *path, const char *src, size_t lenif (err)
convert.c static int apply_multi_file_filter(const char *path, const char *src, size_t len · line 890
Quoted 10 lines
convert.c +4
-3goto done; -if (fd >=0 ) - err =write_packetized_from_fd (fd, process->in); -else +if (fd >=0 ) { +struct packet_scratch_space scratch; + err =write_packetized_from_fd (fd, process->in, &scratch); + }else err =write_packetized_from_buf (src, len, process->in);
Isn't this just putting the buffer onto the stack anyway? Your scratch_space struct is really just a big array. You'd want to make it static here, but then we haven't really solved anything. :)
I think instead that:
Quoted 10 lines
convert.c +2
-3-int write_packetized_from_fd (int fd_in,int fd_out) +int write_packetized_from_fd (int fd_in,int fd_out, +struct packet_scratch_space *scratch) { -static char buf[LARGE_PACKET_DATA_MAX ];int err =0 ;ssize_t bytes_to_write;while (!err) { - bytes_to_write =xread (fd_in, buf,sizeof (buf));
convert.c static int apply_multi_file_filter(const char *path, const char *src, size_t len
Quoted 10 lines
convert.c +4
-1+ bytes_to_write =xread (fd_in, scratch->buffer, +sizeof (scratch->buffer));if (bytes_to_write <0 )return COPY_READ_ERROR ;if (bytes_to_write ==0 )break ; - err =packet_write_gently (fd_out, buf, bytes_to_write); + err =packet_write_gently (fd_out, scratch->buffer, + bytes_to_write); }
...just heap-allocating the buffer in this function would be fine. It's one malloc for the whole sequence of pktlines, which is unlikely to be a problem.
-Peff
Re: [PATCH v4 07/12] unix-socket: elimiate static unix_stream_socket() helper function
In reply to Jeff Hostetler via GitGitGadgetQuoted 5 lines · Jeff Hostetler via GitGitGadget
From: Jeff Hostetler <[email protected]>
The static helper function
unix_stream_socket()callsdie(). This is not appropriate for all callers. Eliminate the wrapper function and make the callers propagate the error.
Thanks for breaking it up this way. It's (IMHO) much easier to see the motivation and impact of the changes now.
There's a small typo in the subject:
Quoted 1 line
Subject: unix-socket: elimiate static
unix_stream_socket()helper function
-Peff
Re: [PATCH v4 08/12] unix-socket: add backlog size option to unix_stream_listen()
In reply to Jeff Hostetler via GitGitGadgetunix-socket.c int unix_stream_listen(const char *path) · line 115
Quoted 10 lines · Jeff Hostetler via GitGitGadget
unix-socket.c +4
-1@@ -106,7 +108,10 @@ int unix_stream_listen(const char *path)if (bind (fd, (struct sockaddr *)&sa,sizeof (sa)) <0 ) goto fail; -if (listen (fd,5 ) <0 ) + backlog = opts->listen_backlog_size; +if (backlog <=0 ) + backlog =DEFAULT_UNIX_STREAM_LISTEN_BACKLOG ; +if (listen (fd, backlog) <0 ) goto fail;
OK, so we still have the fallback-on-zero here, which is good...
unix-socket.h line 13
Quoted 10 lines
unix-socket.h +10
-0+struct unix_stream_listen_opts { +int listen_backlog_size; +}; + +#define DEFAULT_UNIX_STREAM_LISTEN_BACKLOG (5) + +#define UNIX_STREAM_LISTEN_OPTS_INIT \ +{ \ + .listen_backlog_size = DEFAULT_UNIX_STREAM_LISTEN_BACKLOG, \ +}
...but I thought the plan was to drop this initialization in favor of a zero-initialization. What you have certainly wouldn't do the wrong thing, but it just seems weirdly redundant. Unless some caller really wants to know what the default will be?
-Peff
Re: [PATCH v4 10/12] unix-socket: create `unix_stream_server__listen_with_lock()`
In reply to Jeff Hostetler via GitGitGadgetQuoted 4 lines · Jeff Hostetler via GitGitGadget
From: Jeff Hostetler <[email protected]>
Create a version of
unix_stream_listen()that uses a ".lock" lockfile to create the unix domain socket in a race-free manner.
The "unix_stream_server__listen_with_lock" name is quite a mouthful. My first question was: don't we have an "options" struct that we can use to tell it we're interested in using the locking strategy?
But I do find it a little weird for the feature to be at this layer at all. I'd have thought it would make more sense in the simple-ipc layer that implements the unix-socket backend, where app-level logic like "it's OK to just connect to this socket and hang up in order to ping it" might be more appropriate. We might even want to have a more robust check (e.g., an actual "ping" that expects the server to say "yes, I'm here").
(But also see below where I am less certain about this...)
Quoted 11 lines
Unix domain sockets have a fundamental problem on Unix systems because they persist in the filesystem until they are deleted. This is independent of whether a server is actually listening for connections. Well-behaved servers are expected to delete the socket when they shutdown. A new server cannot easily tell if a found socket is attached to an active server or is leftover cruft from a dead server. The traditional solution used by
unix_stream_listen()is to force delete the socket pathname and then create a new socket. This solves the latter (cruft) problem, but in the case of the former, it orphans the existing server (by stealing the pathname associated with the socket it is listening on).
Nicely explained.
Quoted 9 lines
We cannot directly use a .lock lockfile to create the socket because the socket is created by
bind(2)rather than theopen(2)mechanism used bytempfile.c.As an alternative, we hold a plain lockfile ("<path>.lock") as a mutual exclusion device. Under the lock, we test if an existing socket ("<path>") is has an active server. If not, create a new socket and begin listening. Then we rollback the lockfile in all cases.
Make sense.
Quoted 12 lines
unix-socket.c +12
-0+static int is_another_server_alive (const char *path, +const struct unix_stream_listen_opts *opts) +{ +struct stat st; +int fd; + +if (!lstat (path, &st) &&S_ISSOCK (st.st_mode)) { +/* +* A socket-inode exists on disk at `path`, but we +* don't know whether it belongs to an active server +* or whether the last server died without cleaning +* up.
unix-socket.c int unix_stream_listen(const char *path, · line 158
Quoted 10 lines
unix-socket.c +10
-0+* +* Poke it with a trivial connection to try to find +* out. +*/ + fd =unix_stream_connect (path, opts->disallow_chdir); +if (fd >=0 ) { +close (fd); +return 1 ; + } + }
The lstat() seems redundant here. unix_stream_connect() will tell us whether there is something to connect to or not. (It's also racy with respect to the actual connect, but since you're doing this under lock, I don't think that matters).
Quoted 6 lines
unix-socket.c +6
-0+struct unix_stream_server_socket *unix_stream_server__listen_with_lock ( +const char *path, +const struct unix_stream_listen_opts *opts) +{ +struct lock_file lock =LOCK_INIT ; +int fd_socket;
unix-socket.c int unix_stream_listen(const char *path, · line 178
Quoted 10 lines
unix-socket.c +10
-0+struct unix_stream_server_socket *server_socket; + +/* +* Create a lock at "<path>.lock" if we can. +*/ +if (hold_lock_file_for_update_timeout (&lock, path,0 , + opts->timeout_ms) <0 ) { +error_errno (_ ("could not lock listener socket '%s'" ), path); +return NULL ; + }
Would you want to ping to see if it's alive before creating the lock? That would be the fast-path if we assume that a server will usually be there once started. Or is that supposed to happen in the caller (in which case I'd again wonder if this really should be happening in the simple-ipc code).
unix-socket.c int unix_stream_listen(const char *path, · line 189
Quoted 10 lines
unix-socket.c +10
-0+/* +* If another server is listening on "<path>" give up. We do not +* want to create a socket and steal future connections from them. +*/ +if (is_another_server_alive (path, opts)) { + errno =EADDRINUSE ; +error_errno (_ ("listener socket already in use '%s'" ), path); +rollback_lock_file (&lock); +return NULL ; + }
Wouldn't this be a "success" case for a caller? They did not open the server themselves, but they are presumably happy that there is one there now to talk to. So do we actually want to print an error to stderr? Likewise, how do they tell the difference between this NULL and the NULL we returned above because we couldn't take the lock? Or the NULL we return below because there is some error creating a listening socket?
I'd think in those three cases you'd want:
- if lock contention, pause a moment and wait for the winner to spin up and serve requests
- if another server is live while we hold the lock, then we raced them and they won. Release the lock and start using them.
- if we really tried to call unix_stream_listen() and that failed, give up now. There is some system error that is not likely to be fixed by trying anything more (e.g., ENAMETOOLONG).
unix-socket.c int unix_stream_listen(const char *path, · line 203
Quoted 3 lines
unix-socket.c +3
-0+ server_socket =xcalloc (1 ,sizeof (*server_socket)); + server_socket->path_socket =strdup (path); + server_socket->fd_socket = fd_socket;
What do we need this server_socket for? The caller already knows the path; they fed it to us. We do need to return the descriptor, but we could do that directly.
unix-socket.c int unix_stream_listen(const char *path, · line 204
Quoted 1 line
unix-socket.c +1
-0+lstat (path, &server_socket->st_socket);
This lstat I guess is part of your "periodically check to see if we're still the one holding the socket" strategy. We _shouldn't_ need that anymore, with the dotlocking, but I'm OK with it as a belt-and-suspenders check. But why are we filling in the lstat here? This seems like something that the unix-socket code doesn't really need to know about (though you do at least provide the complementary "was_stolen" function here, so that part makes sense).
Again, I guess I'd find it less weird if it were happening at a layer above. Maybe I'm really just complaining that this is in unix-socket.c. I guess it is a separate unix_stream_server data type. Arguably that should go in a separate file, but I guess the whole conditional compilation of unix-socket.c makes that awkward. So maybe this is the least-bad thing.
unix-socket.c int unix_stream_listen(const char *path, · line 214
Quoted 9 lines
unix-socket.c +9
-0+/* +* Always rollback (just delete) "<path>.lock" because we already created +* "<path>" as a socket and do not want to commit_lock to do the atomic +* rename trick. +*/ +rollback_lock_file (&lock); + +return server_socket; +}
OK, this part makes sense to me.
Quoted 5 lines
unix-socket.c +5
-0+void unix_stream_server__free ( +struct unix_stream_server_socket *server_socket) +{ +if (!server_socket) +return ;
unix-socket.c int unix_stream_listen(const char *path, · line 230
Quoted 10 lines
unix-socket.c +10
-0+ +if (server_socket->fd_socket >=0 ) { +if (!unix_stream_server__was_stolen (server_socket)) +unlink (server_socket->path_socket); +close (server_socket->fd_socket); + } + +free (server_socket->path_socket); +free (server_socket); +}
OK, this makes sense. We only remove it if we're still the ones holding it. That's not done under lock, though, so it's possibly racy (somebody steals from us while _they_ hold the lock; we check and see "not stolen" right before they steal it, and then we unlink their stolen copy).
Quoted 8 lines
unix-socket.c +8
-0+int unix_stream_server__was_stolen ( +struct unix_stream_server_socket *server_socket) +{ +struct stat st_now; + +if (!server_socket) +return 0 ; +
unix-socket.c int unix_stream_listen(const char *path, · line 249
Quoted 10 lines
unix-socket.c +10
-0+if (lstat (server_socket->path_socket, &st_now) ==-1 ) +return 1 ; + +if (st_now.st_ino != server_socket->st_socket.st_ino) +return 1 ; + +/* We might also consider the ctime on some platforms. */ + +return 0 ; +}
You probably should confirm that st.dev matches, too, since that is the namespace for st.ino. Maybe also double check that it's still a socket with S_ISSOCK(st_mode)?
-Peff
Quoted 8 lines · Junio C Hamano
Quoted 6 lines · Jeff Hostetler via GitGitGadget
Here is V4 of my "Simple IPC" series. It addresses Gábor's comment WRT shutting down the server to make unit tests more predictable on CI servers.
(https://lore.kernel.org/git/[email protected])Jeff
Quoted 8 lines
Quoted 3 lines
cc: Ævar Arnfjörð Bjarmason [email protected] cc: Jeff Hostetler [email protected] cc: Jeff King [email protected] cc: Chris Torek [email protected]
It seems that the discussions around the topic has mostly done during the v2 review, and has quieted down since then.
Let's merge it down to 'next'?
Sorry, I hadn't gotten around to looking at the latest version. I left another round of comments. Some of them are arguably bikeshedding, but there's at least one I think we'd want to address (the big stack buffer in patch 1).
I also haven't carefully looked at the simple-ipc design at all; my focus has just been on the details of socket and pktline code being touched. Since there are no simple-ipc users yet, and since it's internal and would be easy to change later, I'm mostly content for Jeff to proceed as he sees fit and iterate on it as necessary.
-Peff
Re: [PATCH v4 01/12] pkt-line: eliminate the need for static buffer in packet_write_gently()
In reply to Jeff KingQuoted 15 lines · Jeff King
Quoted 2 lines · Jeff Hostetler via GitGitGadget
Change the API of
write_packetized_from_fd()to accept a scratch space argument from its caller to avoid similar issues here.OK, but...
Quoted 8 lines
convert.c +0
-0@@ -883,9 +883,10 @@ static int apply_multi_file_filter(const char *path, const char *src, size_t lenif (err) goto done;
Quoted 12 lines
convert.c static int apply_multi_file_filter(const char *path, const char *src, size_t len · line 890
Quoted 8 lines
convert.c +4
-3-if (fd >=0 ) - err =write_packetized_from_fd (fd, process->in); -else +if (fd >=0 ) { +struct packet_scratch_space scratch; + err =write_packetized_from_fd (fd, process->in, &scratch); + }else err =write_packetized_from_buf (src, len, process->in);Isn't this just putting the buffer onto the stack anyway? Your
scratch_spacestruct is really just a big array. You'd want to make it static here, but then we haven't really solved anything. :)
Yeah, I was letting the caller decide how to provide the buffer. They could put it on the stack or allocate it once across a whole set of files or use a static buffer -- the caller has context for what works best that we don't have here. For example, the caller may know that is not in threaded code at all, but we cannot assume that here.
Quoted 11 lines
I think instead that:
Quoted 8 lines
convert.c +2
-2-int write_packetized_from_fd (int fd_in,int fd_out) +int write_packetized_from_fd (int fd_in,int fd_out, +struct packet_scratch_space *scratch) { -static char buf[LARGE_PACKET_DATA_MAX ];int err =0 ;ssize_t bytes_to_write;
Quoted 16 lines
convert.c
Quoted 12 lines
while (!err) {convert.c +4
-2- bytes_to_write =xread (fd_in, buf,sizeof (buf)); + bytes_to_write =xread (fd_in, scratch->buffer, +sizeof (scratch->buffer));if (bytes_to_write <0 )return COPY_READ_ERROR ;if (bytes_to_write ==0 )break ; - err =packet_write_gently (fd_out, buf, bytes_to_write); + err =packet_write_gently (fd_out, scratch->buffer, + bytes_to_write); }...just heap-allocating the buffer in this function would be fine. It's one malloc for the whole sequence of pktlines, which is unlikely to be a problem.
Right, I think it would be fine to malloc it here, but I didn't want to assume that everyone would think that.
I'll change it.
Thanks
Jeff
Quoted 24 lines · Jeff King
Quoted 8 lines · Junio C Hamano
Quoted 6 lines · Jeff Hostetler via GitGitGadget
Here is V4 of my "Simple IPC" series. It addresses Gábor's comment WRT shutting down the server to make unit tests more predictable on CI servers.
(https://lore.kernel.org/git/[email protected])Jeff
Quoted 8 lines
Quoted 3 lines
cc: Ævar Arnfjörð Bjarmason [email protected] cc: Jeff Hostetler [email protected] cc: Jeff King [email protected] cc: Chris Torek [email protected]
It seems that the discussions around the topic has mostly done during the v2 review, and has quieted down since then.
Let's merge it down to 'next'?
Sorry, I hadn't gotten around to looking at the latest version. I left another round of comments. Some of them are arguably bikeshedding, but there's at least one I think we'd want to address (the big stack buffer in patch 1).
Quoted 8 lines
I also haven't carefully looked at the simple-ipc design at all; my focus has just been on the details of socket and pktline code being touched. Since there are no simple-ipc users yet, and since it's internal and would be easy to change later, I'm mostly content for Jeff to proceed as he sees fit and iterate on it as necessary.
-Peff
We can wait until next week on moving this 'next' if you want. I'll attend to the buffer alloc in patch 1. I'm still reading the other comments and will see where that takes me.
I'm about ready to push an RFC for my fsmonitor--daemon series that sits on top of this simple-ipc series, so you can see an actual use case if that would help understand (my madness).
Thanks
Jeff
Re: [PATCH v4 01/12] pkt-line: eliminate the need for static buffer in packet_write_gently()
In reply to Jeff HostetlerQuoted 13 lines · Jeff Hostetler
Quoted 4 lines
Quoted 4 lines
convert.c +1
-3-if (fd >=0 ) - err =write_packetized_from_fd (fd, process->in); -else +if (fd >=0 ) {Quoted 8 lines
convert.c static int apply_multi_file_filter(const char *path, const char *src, size_t len · line 890
Quoted 4 lines
convert.c +3
-0+struct packet_scratch_space scratch; + err =write_packetized_from_fd (fd, process->in, &scratch); + }else err =write_packetized_from_buf (src, len, process->in);Isn't this just putting the buffer onto the stack anyway? Your
scratch_spacestruct is really just a big array. You'd want to make it static here, but then we haven't really solved anything. :)
Quoted 6 lines
Yeah, I was letting the caller decide how to provide the buffer. They could put it on the stack or allocate it once across a whole set of files or use a static buffer -- the caller has context for what works best that we don't have here. For example, the caller may know that is not in threaded code at all, but we cannot assume that here.
Yeah, I think it's successfully pushed the problem up to the caller. But it introduced a _new_ problem in putting the large buffer on the stack. So if this were "static struct packet_scratch_space scratch", I think we'd be OK.
And perhaps that would meet your needs (if you just need to call write_packed_from_fd() in a thread, and not this other caller).
But I do think the heap approach is nice in that it keeps the interface clean, and I think the performance should be comparable.
Quoted 4 lines
Right, I think it would be fine to malloc it here, but I didn't want to assume that everyone would think that.
I'll change it.
Thanks. :)
-Peff
Quoted 11 lines · Jeff Hostetler
Quoted 10 lines
Sorry, I hadn't gotten around to looking at the latest version. I left another round of comments. Some of them are arguably bikeshedding, but there's at least one I think we'd want to address (the big stack buffer in patch 1).
I also haven't carefully looked at the simple-ipc design at all; my focus has just been on the details of socket and pktline code being touched. Since there are no simple-ipc users yet, and since it's internal and would be easy to change later, I'm mostly content for Jeff to proceed as he sees fit and iterate on it as necessary.
Quoted 3 lines
We can wait until next week on moving this 'next' if you want. I'll attend to the buffer alloc in patch 1. I'm still reading the other comments and will see where that takes me.
I could have been a bit more clear here: modulo any response you have to my latest round of comments, I'm mostly happy to let this proceed to next. So I was thinking you'd have one more re-roll dealing with the patch 1 problems plus anything else you think worth addressing from my batch of comments, and then that result would probably be ready for 'next'.
Quoted 3 lines
I'm about ready to push an RFC for my fsmonitor--daemon series that sits on top of this simple-ipc series, so you can see an actual use case if that would help understand (my madness).
I may have dug my own grave here. ;) I'm actually not incredibly interested in the overall topic. So I wasn't saying so much "I'll reserve judgement on simple-ipc until I see callers" so much as "I expect you'll find any shortcomings in its design yourself as you build on top of it".
And by "not interested" I don't mean that I think the topic is without value. Far from it; I think this is an important area to be working in. But it's complex and time-consuming to review. So I was hoping somebody with more expertise and interest in the problem space would do that part of the review, and I could continue to focus on other stuff. That may be wishful thinking, though. :)
-Peff
Re: [PATCH v4 12/12] t0052: add simple-ipc tests and t/helper/test-simple-ipc tool
In reply to Jeff Hostetler via GitGitGadgetQuoted 2 lines · Jeff Hostetler via GitGitGadget
Create t/helper/test-simple-ipc test tool to exercise the "simple-ipc" functions.
BTW, one oddity I noticed in this (because of my -Wunused-parameters branch):
Quoted 19 lines
t/helper/test-simple-ipc.c new file +19
-0+#ifndef GIT_WINDOWS_NATIVE +/* +* This is adapted from `daemonize()`. Use `fork()` to directly create and +* run the daemon in a child process. +*/ +static int spawn_server (const char *path, +const struct ipc_server_opts *opts, +pid_t *pid) +{ + *pid =fork (); + +switch (*pid) { +case 0 : +if (setsid () ==-1 ) +error_errno (_ ("setsid failed" )); +close (0 ); +close (1 ); +close (2 ); +sanitize_stdfds ();
t/helper/test-simple-ipc.c line 277
Quoted 10 lines
t/helper/test-simple-ipc.c new file +10
-0+ +return ipc_server_run (path, opts, test_app_cb, (void *)&my_app_data); + +case -1 : +return error_errno (_ ("could not spawn daemon in the background" )); + +default : +return 0 ; + } +}
In the non-Windows version, we spawn a server using the "path" parameter we got from the caller.
But in the Windows version:
Quoted 26 lines
t/helper/test-simple-ipc.c new file +26
-0+#else +/* +* Conceptually like `daemonize()` but different because Windows does not +* have `fork(2)`. Spawn a normal Windows child process but without the +* limitations of `start_command()` and `finish_command()`. +*/ +static int spawn_server (const char *path, +const struct ipc_server_opts *opts, +pid_t *pid) +{ +char test_tool_exe[MAX_PATH ]; +struct strvec args =STRVEC_INIT ; +int in, out; + +GetModuleFileNameA (NULL , test_tool_exe,MAX_PATH ); + + in =open ("/dev/null" ,O_RDONLY ); + out =open ("/dev/null" ,O_WRONLY ); + +strvec_push (&args, test_tool_exe); +strvec_push (&args,"simple-ipc" ); +strvec_push (&args,"run-daemon" ); +strvec_pushf (&args,"--threads=%d" , opts->nr_threads); + + *pid =mingw_spawnvpe (args.v[0 ], args.v,NULL ,NULL , in, out, out); +close (in);
t/helper/test-simple-ipc.c line 313
Quoted 10 lines
t/helper/test-simple-ipc.c new file +10
-0+close (out); + +strvec_clear (&args); + +if (*pid <0 ) +return error (_ ("could not spawn daemon in the background" )); + + return 0; +} +#endif
We ignore the "path" parameter entirely. Should we be passing it along as an option to the child process? I think it doesn't really matter at this point because both the parent and child processes will use the hard-coded string "ipc-test", but it seems like something the test script might want to be able to specify.
-Peff
Re: [PATCH v4 10/12] unix-socket: create `unix_stream_server__listen_with_lock()`
In reply to Jeff KingQuoted 11 lines · Jeff King
Quoted 4 lines · Jeff Hostetler via GitGitGadget
From: Jeff Hostetler <[email protected]>
Create a version of
unix_stream_listen()that uses a ".lock" lockfile to create the unix domain socket in a race-free manner.The "
unix_stream_server__listen_with_lock" name is quite a mouthful. My first question was: don't we have an "options" struct that we can use to tell it we're interested in using the locking strategy?
Quoted 7 lines
But I do find it a little weird for the feature to be at this layer at all. I'd have thought it would make more sense in the simple-ipc layer that implements the unix-socket backend, where app-level logic like "it's OK to just connect to this socket and hang up in order to ping it" might be more appropriate. We might even want to have a more robust check (e.g., an actual "ping" that expects the server to say "yes, I'm here").
I think when I started this, the "safe listen" was much closer to the original unix_stream_listen() and it made sense to keep it nearby, but as it evolved (and we added lockfiles and etc.) it grew to be more like its own level between the original socket code and the simple-ipc layer. Pulling it out into its own source file is probably a good idea for clarity.
I was thinking that the "ping" is just to see if a server is listening or not. (And I viewed that as kind of a hack, but it works.) If we start sending data back and forth, we get into protocols and blocking and stuff that this layer (even if we move it up a level) doesn't know about.
I'll pull this out into a new file.
Quoted 48 lines
(But also see below where I am less certain about this...)
Quoted 11 lines
Unix domain sockets have a fundamental problem on Unix systems because they persist in the filesystem until they are deleted. This is independent of whether a server is actually listening for connections. Well-behaved servers are expected to delete the socket when they shutdown. A new server cannot easily tell if a found socket is attached to an active server or is leftover cruft from a dead server. The traditional solution used by
unix_stream_listen()is to force delete the socket pathname and then create a new socket. This solves the latter (cruft) problem, but in the case of the former, it orphans the existing server (by stealing the pathname associated with the socket it is listening on).Nicely explained.
Quoted 9 lines
We cannot directly use a .lock lockfile to create the socket because the socket is created by
bind(2)rather than theopen(2)mechanism used bytempfile.c.As an alternative, we hold a plain lockfile ("<path>.lock") as a mutual exclusion device. Under the lock, we test if an existing socket ("<path>") is has an active server. If not, create a new socket and begin listening. Then we rollback the lockfile in all cases.
Make sense.
Quoted 19 lines
unix-socket.c +19
-0+static int is_another_server_alive (const char *path, +const struct unix_stream_listen_opts *opts) +{ +struct stat st; +int fd; + +if (!lstat (path, &st) &&S_ISSOCK (st.st_mode)) { +/* +* A socket-inode exists on disk at `path`, but we +* don't know whether it belongs to an active server +* or whether the last server died without cleaning +* up. +* +* Poke it with a trivial connection to try to find +* out. +*/ + fd =unix_stream_connect (path, opts->disallow_chdir); +if (fd >=0 ) { +close (fd);
Quoted 8 lines
unix-socket.c int unix_stream_listen(const char *path, · line 158
Quoted 3 lines
unix-socket.c +3
-0+return 1 ; + } + }The
lstat()seems redundant here.unix_stream_connect()will tell us whether there is something to connect to or not. (It's also racy with respect to the actual connect, but since you're doing this under lock, I don't think that matters).
I agree. I'll get rid of the lstat().
Quoted 15 lines
Quoted 15 lines
unix-socket.c +14
-0+struct unix_stream_server_socket *unix_stream_server__listen_with_lock ( +const char *path, +const struct unix_stream_listen_opts *opts) +{ +struct lock_file lock =LOCK_INIT ; +int fd_socket; +struct unix_stream_server_socket *server_socket; + +/* +* Create a lock at "<path>.lock" if we can. +*/ +if (hold_lock_file_for_update_timeout (&lock, path,0 , + opts->timeout_ms) <0 ) { +error_errno (_ ("could not lock listener socket '%s'" ), path);
Quoted 8 lines
Review
Quoted 2 lines
(quoted diff) +2
-0+ return NULL; + }Would you want to ping to see if it's alive before creating the lock? That would be the fast-path if we assume that a server will usually be there once started. Or is that supposed to happen in the caller (in which case I'd again wonder if this really should be happening in the simple-ipc code).
Starting a server should not happen that often, so I'm not sure it matters. And yes, a server once started should run for a long time. Pinging without the lock puts us back in another race, so we might as well lock first.
Quoted 24 lines
unix-socket.c int unix_stream_listen(const char *path, · line 189
Quoted 11 lines
unix-socket.c +10
-0+/* +* If another server is listening on "<path>" give up. We do not +* want to create a socket and steal future connections from them. +*/ +if (is_another_server_alive (path, opts)) { + errno =EADDRINUSE ; +error_errno (_ ("listener socket already in use '%s'" ), path); +rollback_lock_file (&lock); +return NULL ; + }Wouldn't this be a "success" case for a caller? They did not open the server themselves, but they are presumably happy that there is one there now to talk to. So do we actually want to print an error to stderr? Likewise, how do they tell the difference between this NULL and the NULL we returned above because we couldn't take the lock? Or the NULL we return below because there is some error creating a listening socket?
I'd think in those three cases you'd want:
- if lock contention, pause a moment and wait for the winner to spin up and serve requests
Quoted 6 lines
- if another server is live while we hold the lock, then we raced them and they won. Release the lock and start using them.
- if we really tried to call
unix_stream_listen()and that failed, give up now. There is some system error that is not likely to be fixed by trying anything more (e.g., ENAMETOOLONG).
Yes, I want to move the error messages out of these library layers.
And yes, if another server is running, our server instance should shutdown gracefully. Other client processes can just talk to them rather than us.
Quoted 8 lines
unix-socket.c int unix_stream_listen(const char *path, · line 203
Quoted 4 lines
unix-socket.c +3
-0+ server_socket =xcalloc (1 ,sizeof (*server_socket)); + server_socket->path_socket =strdup (path); + server_socket->fd_socket = fd_socket;What do we need this
server_socketfor? The caller already knows the path; they fed it to us. We do need to return the descriptor, but we could do that directly.
I wanted a wrapper struct to persist a copy of the pathname near the fd. Later when we get ready to shutdown, we can close and unlink without worrying whether our caller kept their copy of the path buffer.
This also lets me have the pathname to poll and check for theft during the accept thread's event loop.
Quoted 10 lines
unix-socket.c int unix_stream_listen(const char *path, · line 204
Quoted 2 lines
unix-socket.c +1
-0+lstat (path, &server_socket->st_socket);This lstat I guess is part of your "periodically check to see if we're still the one holding the socket" strategy. We _shouldn't_ need that anymore, with the dotlocking, but I'm OK with it as a belt-and-suspenders check. But why are we filling in the lstat here? This seems like something that the unix-socket code doesn't really need to know about (though you do at least provide the complementary "
was_stolen" function here, so that part makes sense).
The dotlock is only on disk for the duration of the socket setup. We do the rollback (to delete the lockfile) once we have the socket open and ready for business.
The lstat gives me the inode of the socket on disk and we can watch it with future lstat's in the event loop and see if it changes and detect theft and auto-shutdown.
Quoted 7 lines
Again, I guess I'd find it less weird if it were happening at a layer above. Maybe I'm really just complaining that this is in
unix-socket.c. I guess it is a separateunix_stream_serverdata type. Arguably that should go in a separate file, but I guess the whole conditional compilation ofunix-socket.cmakes that awkward. So maybe this is the least-bad thing.
Yeah, I'll move it out.
And yes, the whole conditional compilation thing was something I was hesitating on, but it really isn't that bad. (But I should not brag here until all of the build servers have had their say....)
Quoted 25 lines
unix-socket.c int unix_stream_listen(const char *path, · line 214
Quoted 10 lines
unix-socket.c +9
-0+/* +* Always rollback (just delete) "<path>.lock" because we already created +* "<path>" as a socket and do not want to commit_lock to do the atomic +* rename trick. +*/ +rollback_lock_file (&lock); + +return server_socket; +}OK, this part makes sense to me.
Quoted 12 lines
unix-socket.c +12
-0+void unix_stream_server__free ( +struct unix_stream_server_socket *server_socket) +{ +if (!server_socket) +return ; + +if (server_socket->fd_socket >=0 ) { +if (!unix_stream_server__was_stolen (server_socket)) +unlink (server_socket->path_socket); +close (server_socket->fd_socket); + } +
Quoted 8 lines
unix-socket.c int unix_stream_listen(const char *path, · line 230
Quoted 3 lines
unix-socket.c +3
-0+free (server_socket->path_socket); +free (server_socket); +}OK, this makes sense. We only remove it if we're still the ones holding it. That's not done under lock, though, so it's possibly racy (somebody steals from us while _they_ hold the lock; we check and see "not stolen" right before they steal it, and then we unlink their stolen copy).
Right, I didn't bother with the lock here. I don't think we need it.
We technically still have the socket open and are listening on it when we lstat and unlink it. The other process should create the lock and try to connect. That should hang in the kernel because of the accept() grace period. Then we close the socket and the client's connection request errors because we didn't accept it. They will see the error as no one is listening and then create their own socket.
Quoted 15 lines
Quoted 15 lines
unix-socket.c +14
-0+int unix_stream_server__was_stolen ( +struct unix_stream_server_socket *server_socket) +{ +struct stat st_now; + +if (!server_socket) +return 0 ; + +if (lstat (server_socket->path_socket, &st_now) ==-1 ) +return 1 ; + +if (st_now.st_ino != server_socket->st_socket.st_ino) +return 1 ; +
Quoted 8 lines
unix-socket.c int unix_stream_listen(const char *path, · line 249
Quoted 4 lines
unix-socket.c +4
-0+/* We might also consider the ctime on some platforms. */ + +return 0 ; +}You probably should confirm that st.dev matches, too, since that is the namespace for st.ino. Maybe also double check that it's still a socket with
S_ISSOCK(st_mode)?
Good point.
Quoted 3 lines
-Peff
Thanks for all the careful study. I'll push up a new series to address them shortly.
Jeff
Re: [PATCH v4 12/12] t0052: add simple-ipc tests and t/helper/test-simple-ipc tool
In reply to Jeff KingQuoted 86 lines · Jeff King
Quoted 2 lines · Jeff Hostetler via GitGitGadget
Create t/helper/test-simple-ipc test tool to exercise the "simple-ipc" functions.
BTW, one oddity I noticed in this (because of my -Wunused-parameters branch):
Quoted 19 lines
t/helper/test-simple-ipc.c new file +19
-0+#ifndef GIT_WINDOWS_NATIVE +/* +* This is adapted from `daemonize()`. Use `fork()` to directly create and +* run the daemon in a child process. +*/ +static int spawn_server (const char *path, +const struct ipc_server_opts *opts, +pid_t *pid) +{ + *pid =fork (); + +switch (*pid) { +case 0 : +if (setsid () ==-1 ) +error_errno (_ ("setsid failed" )); +close (0 ); +close (1 ); +close (2 ); +sanitize_stdfds ();t/helper/test-simple-ipc.c line 277
Quoted 10 lines
t/helper/test-simple-ipc.c new file +10
-0+ +return ipc_server_run (path, opts, test_app_cb, (void *)&my_app_data); + +case -1 : +return error_errno (_ ("could not spawn daemon in the background" )); + +default : +return 0 ; + } +}In the non-Windows version, we spawn a server using the "path" parameter we got from the caller.
But in the Windows version:
Quoted 26 lines
t/helper/test-simple-ipc.c new file +26
-0+#else +/* +* Conceptually like `daemonize()` but different because Windows does not +* have `fork(2)`. Spawn a normal Windows child process but without the +* limitations of `start_command()` and `finish_command()`. +*/ +static int spawn_server (const char *path, +const struct ipc_server_opts *opts, +pid_t *pid) +{ +char test_tool_exe[MAX_PATH ]; +struct strvec args =STRVEC_INIT ; +int in, out; + +GetModuleFileNameA (NULL , test_tool_exe,MAX_PATH ); + + in =open ("/dev/null" ,O_RDONLY ); + out =open ("/dev/null" ,O_WRONLY ); + +strvec_push (&args, test_tool_exe); +strvec_push (&args,"simple-ipc" ); +strvec_push (&args,"run-daemon" ); +strvec_pushf (&args,"--threads=%d" , opts->nr_threads); + + *pid =mingw_spawnvpe (args.v[0 ], args.v,NULL ,NULL , in, out, out); +close (in);t/helper/test-simple-ipc.c line 313
Quoted 10 lines
t/helper/test-simple-ipc.c new file +10
-0+close (out); + +strvec_clear (&args); + +if (*pid <0 ) +return error (_ ("could not spawn daemon in the background" )); + + return 0; +} +#endif We ignore the "path" parameter entirely. Should we be passing it along as an option to the child process? I think it doesn't really matter at this point because both the parent and child processes will use the hard-coded string "ipc-test", but it seems like something the test script might want to be able to specify.
Quoted 2 lines
-Peff
Yeah, since it was a test helper I hesitated to add a command line arg to pass it to the child process (when all callers were right here and using the same default value). However it would be good to do so in case we want to write more complicated tests.
Jeff
Re: [PATCH v4 05/12] simple-ipc: design documentation for new IPC mechanism
In reply to Jeff Hostetler via GitGitGadgetDocumentation/technical/api-simple-ipc.txt line 24
Quoted 3 lines · Jeff Hostetler via GitGitGadget
Documentation/technical/api-simple-ipc.txt new file +3
-0+How the simple-ipc server is started is also outside the scope of the +IPC mechanism. For example, the server might be started during +maintenance operations.
Just a tiny nit.
I would expect to see "might be <re>started" if it is followed by "during maintenance operations"; in other words, I expect "might be started" to be followed by "as part of the boot-up sequence".
Documentation/technical/api-simple-ipc.txt line 30
Quoted 5 lines
Documentation/technical/api-simple-ipc.txt new file +5
-0+The IPC protocol consists of a single request message from the client and +an optional request message from the server. For simplicity, pkt-line +routines are used to hide chunking and buffering concerns. Each side +terminates their message with a flush packet. +(Documentation/technical/protocol-common.txt)
Hidign chunking and buffering concerns is good, but it introduces some limitations, like 64k chunk limit, which probably want to be mentioned (if not explained or described) here.
Do we give any extra meaning over "here, a message ends" to the flush packet? The lack of "now it is your turn to speak" (aka "delim") has long been a weakness of the over-the-wire protocol, and we'd probably want to learn from the past experience.
Documentation/technical/api-simple-ipc.txt line 34
Quoted 3 lines
Documentation/technical/api-simple-ipc.txt new file +3
-0+The actual format of the client and server messages is application +specific. The IPC layer transmits and receives an opaque buffer without +any concern for the content within.
Please sell why such a semantic-agnostic layer exists and what benefit the callers would get out of it. Perhaps you offer some mechanism to allow them to send and receive without having to worry about deadlocks[*]?
THanks.
[Footnote]
*1* ...just an example benefit that may or may not exist.
Re: [PATCH v4 07/12] unix-socket: elimiate static unix_stream_socket() helper function
In reply to Jeff Hostetler via GitGitGadgetQuoted 18 lines · Jeff Hostetler via GitGitGadget
int
unix_stream_connect(const char *path)
{unix-socket.c +5
-2-int fd, saved_errno; +int fd =-1 , saved_errno;struct sockaddr_un sa;struct unix_sockaddr_context ctx;if (unix_sockaddr_init (&sa, path, &ctx) <0 )return -1 ; - fd =unix_stream_socket (); + fd =socket (AF_UNIX ,SOCK_STREAM ,0 ); +if (fd <0 ) + goto fail; +if (connect (fd, (struct sockaddr *)&sa,sizeof (sa)) <0 ) goto fail;unix_sockaddr_cleanup (&ctx);
unix-socket.c int unix_stream_connect(const char *path) · line 90
Quoted 10 lines
unix-socket.c +2
-1@@ -87,15 +82,16 @@ int unix_stream_connect(const char *path) fail: saved_errno = errno; +if (fd !=-1 ) +close (fd);unix_sockaddr_cleanup (&ctx); -close (fd); errno = saved_errno;return -1 ; }
So, the difference is that the caller must be prepared to see and handle error return from this function when creating socket fails, but existing callers must be prepared to handle error returns from this function for different reasons (e.g. we may successfully make a socket, but connect may fail) already anyway, so this should be a fairly safe thing to do. The sole caller send_request() in credential-cache.c will relay the error return back to do_cache() which cares what errno it got, and that code does seem to care what kind of error caused unix_stream_connect() to fail. And the new error case introduced by this patch won't result in ENOENT or ECONNREFUSED to cause the code to fall back to "if the thing is not running, let's try starting it and try again".
OK.
unix-socket.c int unix_stream_connect(const char *path) · line 93
Quoted 2 lines
unix-socket.c +0
-0int unix_stream_listen (const char *path) {
This one is simpler to vet its caller. It immediately dies upon any error return.
Thanks.
Re: [PATCH v4 08/12] unix-socket: add backlog size option to unix_stream_listen()
In reply to Jeff KingQuoted 6 lines · Jeff King
Quoted 4 lines · Jeff Hostetler via GitGitGadget
unix-socket.c +0
-0@@ -106,7 +108,10 @@ int unix_stream_listen(const char *path)if (bind (fd, (struct sockaddr *)&sa,sizeof (sa)) <0 ) goto fail;
Quoted 6 lines
unix-socket.c int unix_stream_listen(const char *path) · line 115
Quoted 6 lines
unix-socket.c +4
-1-if (listen (fd,5 ) <0 ) + backlog = opts->listen_backlog_size; +if (backlog <=0 ) + backlog =DEFAULT_UNIX_STREAM_LISTEN_BACKLOG ; +if (listen (fd, backlog) <0 ) goto fail;
Luckily there is no "pass 0 and the platforms will choose an appropriate backlog value", so "pass 0 to get the default Git chooses" is OK, but do we even want to allow passing any negative value? Shouldn't it be diagnosed as an error instead?
Quoted 17 lines
OK, so we still have the fallback-on-zero here, which is good...
unix-socket.h line 13
Quoted 10 lines
unix-socket.h +10
-0+struct unix_stream_listen_opts { +int listen_backlog_size; +}; + +#define DEFAULT_UNIX_STREAM_LISTEN_BACKLOG (5) + +#define UNIX_STREAM_LISTEN_OPTS_INIT \ +{ \ + .listen_backlog_size = DEFAULT_UNIX_STREAM_LISTEN_BACKLOG, \ +}...but I thought the plan was to drop this initialization in favor of a zero-initialization. What you have certainly wouldn't do the wrong thing, but it just seems weirdly redundant. Unless some caller really wants to know what the default will be?
Very true. The code knows the exact value input 0 has to fall back to; we shouldn't have to initialize to that same exact value and I do not offhand see why the DEFAULT_UNIX_STREAM_LISTEN_BACKLOG needs to be a public constant.
Thanks.
Re: [PATCH v4 01/12] pkt-line: eliminate the need for static buffer in packet_write_gently()
In reply to Jeff HostetlerQuoted 4 lines · Jeff Hostetler
Right, I think it would be fine to malloc it here, but I didn't want to assume that everyone would think that.
I'll change it.
I agree with both of you that the code is unnice in its stack usage and we want fix with malloc(), or something like that, but sorry, I think I merged this round by mistake to 'next'.
As we won't be merging the topic to the upcoming release anyway, I am willing to revert the merge to 'next' and requeue an updated one, when it appears (I am also OK to see an incremental update, "oops, no, we realize we don't want to have it on the stack" fix-up, if this is the only glitch in the series that need to be fixed).
Thanks.
Re: [PATCH v4 03/12] pkt-line: (optionally) libify the packet readers
In reply to Johannes Schindelin via GitGitGadget"Johannes Schindelin via GitGitGadget" <[email protected]>
pkt-line.c static int get_packet_data(int fd, char **src_buf, · line 322
Quoted 8 lines · writes:
pkt-line.c +2
-0@@ -313,6 +316,8 @@ static int get_packet_data(int fd, char **src_buf,if (options &PACKET_READ_GENTLE_ON_EOF )return -1 ; +if (options &PACKET_READ_NEVER_DIE ) +return error (_ ("the remote end hung up unexpectedly" ));die (_ ("the remote end hung up unexpectedly" )); }
This hunk treats READ_NEVER_DIE as a less quiet version of GENTRL_ON_EOF, i.e. the new flag allows to continue even after the "hung up unexpectedly" condition that usually causes the process to die..
pkt-line.c enum packet_read_status packet_read_with_status(i · line 381
Quoted 12 lines
pkt-line.c +5
-1@@ -355,12 +363,19 @@ enum packet_read_status packet_read_with_status(i -if ((unsigned )len >= size) +if ((unsigned )len >= size) { +if (options &PACKET_READ_NEVER_DIE ) +return error (_ ("protocol error: bad line length %d" ), + len);die (_ ("protocol error: bad line length %d" ), len); + }if (get_packet_data (fd, src_buffer, src_len, buffer, len, options) <0 ) { *pktlen =-1 ;
In the post-context of this hunk, there is this code:
if ((options & PACKET_READ_DIE_ON_ERR_PACKET) &&
starts_with(buffer, "ERR "))
die(_("remote error: %s"), buffer + 4);
*pktlen = len;
return PACKET_READ_NORMAL;But here, there is no way to override the DIE_ON_ERR with READ_NEVER_DIE.
The asymmetry is somewhat annoying (i.e. if "if you do not want to die upon ERR, don't pass DIE_ON_ERR" could be a valid suggestion to the callers, then "if you do not want to die upon an unexpected hung-up, pass GENTLE_ON_EOF" would equally be valid suggestion), but I'll let it pass.
Quoted 18 lines
pkt-line.h +4
-0@@ -75,10 +75,14 @@ int write_packetized_from_buf_no_flush(const char *src_in, size_t len, int fd_ou* * If options contains PACKET_READ_DIE_ON_ERR_PACKET, it dies when it sees an * ERR packet. +* +* With `PACKET_READ_NEVER_DIE`, no errors are allowed to trigger die() (except +* an ERR packet, when `PACKET_READ_DIE_ON_ERR_PACKET` is in effect). */ #define PACKET_READ_GENTLE_ON_EOF (1u<<0)#define PACKET_READ_CHOMP_NEWLINE (1u<<1)#define PACKET_READ_DIE_ON_ERR_PACKET (1u<<2) +#define PACKET_READ_NEVER_DIE (1u<<3)int packet_read (int fd,char **src_buffer,size_t *src_len,char *buffer,unsigned size,int options);
Quoted 6 lines · Jeff King
And by "not interested" I don't mean that I think the topic is without value. Far from it; I think this is an important area to be working in. But it's complex and time-consuming to review. So I was hoping somebody with more expertise and interest in the problem space would do that part of the review, and I could continue to focus on other stuff. That may be wishful thinking, though. :)
I was not paying close attention to this series, and was planning to visit it before merging it to 'next' but only to ensure that changes to any existing code would not regress existing callers, so it seems that we two have been with pretty much the same attitude;-)
Re: [PATCH v4 09/12] unix-socket: disallow chdir() when creating unix domain sockets
In reply to Jeff Hostetler via GitGitGadgetQuoted 11 lines · Jeff Hostetler via GitGitGadget
From: Jeff Hostetler <[email protected]>
Calls to
chdir()are dangerous in a multi-threaded context. Ifunix_stream_listen()orunix_stream_connect()is given a socket pathname that is too long to fit in asockaddr_unstructure, it willchdir()to the parent directory of the requested socket pathname, create the socket using a relative pathname, and thenchdir()back. This is not thread-safe.Teach
unix_sockaddr_init()to not allow calls tochdir()when this flag is set.
While it is clear that this will not affect any existing callers, I am not sure if this is a good direction to go in the longer term.
I have to wonder if somebody actually relies on this "feature", though. As long as ENAMETOOLONG is passed back to the caller so that it can react to it, any caller that knows it is safe to chdir() at the point of calling "send_request()" should be able to chdir() itself and come back (or fork a child that chdirs and opens a unix domain socket there, and then send the file descriptor back to the parent process).
Thanks.
Re: [PATCH v4 01/12] pkt-line: eliminate the need for static buffer in packet_write_gently()
In reply to Junio C HamanoQuoted 11 lines · Junio C Hamano
Quoted 4 lines · Jeff Hostetler
Right, I think it would be fine to malloc it here, but I didn't want to assume that everyone would think that.
I'll change it.
I agree with both of you that the code is unnice in its stack usage and we want fix with
malloc(), or something like that, but sorry, I think I merged this round by mistake to 'next'.
Quoted 8 lines
As we won't be merging the topic to the upcoming release anyway, I am willing to revert the merge to 'next' and requeue an updated one, when it appears (I am also OK to see an incremental update, "oops, no, we realize we don't want to have it on the stack" fix-up, if this is the only glitch in the series that need to be fixed).
Thanks.
I'm preparing a follow-on patch series to address Peff's comments from Friday/Monday and yours from yesterday. I thought I'd send it as a set of new changes to sit on top of what we have in "next" if that would make things easier for you.
After the upcoming release we can talk about whether it would be better for me to smash together the 2 series or not.
Jeff
Re: [PATCH v4 03/12] pkt-line: (optionally) libify the packet readers
In reply to Junio C HamanoQuoted 39 lines · Junio C Hamano
"Johannes Schindelin via GitGitGadget" <[email protected]>
pkt-line.c static int get_packet_data(int fd, char **src_buf, · line 322
Quoted 8 lines · writes:
pkt-line.c +2
-0@@ -313,6 +316,8 @@ static int get_packet_data(int fd, char **src_buf,if (options &PACKET_READ_GENTLE_ON_EOF )return -1 ; +if (options &PACKET_READ_NEVER_DIE ) +return error (_ ("the remote end hung up unexpectedly" ));die (_ ("the remote end hung up unexpectedly" )); }This hunk treats
READ_NEVER_DIEas a less quiet version ofGENTRL_ON_EOF, i.e. the new flag allows to continue even after the "hung up unexpectedly" condition that usually causes the process to die..pkt-line.c enum packet_read_status packet_read_with_status(i · line 381
Quoted 12 lines
pkt-line.c +5
-1@@ -355,12 +363,19 @@ enum packet_read_status packet_read_with_status(i -if ((unsigned )len >= size) +if ((unsigned )len >= size) { +if (options &PACKET_READ_NEVER_DIE ) +return error (_ ("protocol error: bad line length %d" ), + len);die (_ ("protocol error: bad line length %d" ), len); + }if (get_packet_data (fd, src_buffer, src_len, buffer, len, options) <0 ) { *pktlen =-1 ;In the post-context of this hunk, there is this code:
if ((options & PACKET_READ_DIE_ON_ERR_PACKET) && starts_with(buffer, "ERR ")) die(_("remote error: %s"), buffer + 4); *pktlen = len; return PACKET_READ_NORMAL;
Quoted 8 lines
But here, there is no way to override the
DIE_ON_ERRwithREAD_NEVER_DIE.The asymmetry is somewhat annoying (i.e. if "if you do not want to die upon ERR, don't pass
DIE_ON_ERR" could be a valid suggestion to the callers, then "if you do not want to die upon an unexpected hung-up, passGENTLE_ON_EOF" would equally be valid suggestion), but I'll let it pass.
I agree that there is something odd about all of these flags, but I don't have the context on all the various caller combinations to make a better suggestion at this time. And I certainly don't want to stir up a bigger mess than I already have. :-)
We did document in the .h that READ_NEVER_DIE excludes ERR packets when READ_DIE_ON_ERR is set, so I think we're safe from unexpected surprises.
Quoted 19 lines
Quoted 19 lines
pkt-line.h +4
-0@@ -75,10 +75,14 @@ int write_packetized_from_buf_no_flush(const char *src_in, size_t len, int fd_ou* * If options contains PACKET_READ_DIE_ON_ERR_PACKET, it dies when it sees an * ERR packet. +* +* With `PACKET_READ_NEVER_DIE`, no errors are allowed to trigger die() (except +* an ERR packet, when `PACKET_READ_DIE_ON_ERR_PACKET` is in effect). */ #define PACKET_READ_GENTLE_ON_EOF (1u<<0)#define PACKET_READ_CHOMP_NEWLINE (1u<<1)#define PACKET_READ_DIE_ON_ERR_PACKET (1u<<2) +#define PACKET_READ_NEVER_DIE (1u<<3)int packet_read (int fd,char **src_buffer,size_t *src_len,char *buffer,unsigned size,int options);
Re: [PATCH v4 03/12] pkt-line: (optionally) libify the packet readers
Quoted 18 lines · Jeff Hostetler
Quoted 9 lines
In the post-context of this hunk, there is this code:
if ((options & PACKET_READ_DIE_ON_ERR_PACKET) && starts_with(buffer, "ERR ")) die(_("remote error: %s"), buffer + 4); *pktlen = len; return PACKET_READ_NORMAL;Quoted 8 lines
But here, there is no way to override the
DIE_ON_ERRwithREAD_NEVER_DIE.The asymmetry is somewhat annoying (i.e. if "if you do not want to die upon ERR, don't pass
DIE_ON_ERR" could be a valid suggestion to the callers, then "if you do not want to die upon an unexpected hung-up, passGENTLE_ON_EOF" would equally be valid suggestion), but I'll let it pass.
Quoted 8 lines
I agree that there is something odd about all of these flags, but I don't have the context on all the various caller combinations to make a better suggestion at this time. And I certainly don't want to stir up a bigger mess than I already have. :-)
We did document in the .h that
READ_NEVER_DIEexcludes ERR packets whenREAD_DIE_ON_ERRis set, so I think we're safe from unexpected surprises.
I think the flag is doing sensible things; it's just that the word "never" in the name is confusing, since it is "never except this one time".
Would PACKET_READ_GENTLE_ON_READ_ERROR be a better name, to match GENTLE_ON_EOF? I was tempted to just call it "ON_ERROR", since it also include parsing errors, but maybe somebody would think that includes ERR packets (that is more of a stretch, though, I think).
Likewise, I kind of wonder if callers would really prefer suppressing the error() calls, too. Saying "error: the remote end hung up unexpectedly" is not that helpful if the "remote end" we are talking about is fsmonitor, and not the server side of a fetch.
-Peff
Re: [PATCH v4 09/12] unix-socket: disallow chdir() when creating unix domain sockets
In reply to Junio C HamanoQuoted 17 lines · Junio C Hamano
Quoted 11 lines · Jeff Hostetler via GitGitGadget
From: Jeff Hostetler <[email protected]>
Calls to
chdir()are dangerous in a multi-threaded context. Ifunix_stream_listen()orunix_stream_connect()is given a socket pathname that is too long to fit in asockaddr_unstructure, it willchdir()to the parent directory of the requested socket pathname, create the socket using a relative pathname, and thenchdir()back. This is not thread-safe.Teach
unix_sockaddr_init()to not allow calls tochdir()when this flag is set.While it is clear that this will not affect any existing callers, I am not sure if this is a good direction to go in the longer term.
Quoted 7 lines
I have to wonder if somebody actually relies on this "feature", though. As long as ENAMETOOLONG is passed back to the caller so that it can react to it, any caller that knows it is safe to
chdir()at the point of calling "send_request()" should be able tochdir()itself and come back (or fork a child that chdirs and opens a unix domain socket there, and then send the file descriptor back to the parent process).
The feature is definitely useful; I think I did 1eb10f4091 (unix-socket:
handle long socket pathnames, 2012-01-09) in response to a real problem.
Certainly callers could handle the error themselves. The reason I pushed it down into the socket code was to avoid having to implement in multiple callers. There are only two, but we'd have needed it in both sides (credential-cache--daemon as the listener, and credential-cache as the client).
Ironically, the listening side now does a permanent chdir() to the socket directory anyway, since 6e61449051 (credential-cache--daemon:
change to the socket dir on startup, 2016-02-23). So we could just do that first, and then feed the basename to the socket code.
The client side would still need to handle it, though. It could probably also chdir to the socket directory without any real downside (once started, I don't think the helper program needs to access the filesystem at all outside of the socket).
So I dunno. I'd be OK to just rip the feature out in favor of doing those chdir()s. But that seems like a non-zero amount of work versus leaving, and the existing code has the benefit that if another caller shows up, it could benefit from the feature.
-Peff
Re: [PATCH v4 10/12] unix-socket: create `unix_stream_server__listen_with_lock()`
In reply to Jeff HostetlerQuoted 5 lines · Jeff Hostetler
I was thinking that the "ping" is just to see if a server is listening or not. (And I viewed that as kind of a hack, but it works.) If we start sending data back and forth, we get into protocols and blocking and stuff that this layer (even if we move it up a level) doesn't know about.
Right. Definitely the higher up the stack the ping happens, the more value it has. But I also see the appeal of keeping this as its own layer.
Quoted 12 lines
Quoted 11 lines
unix-socket.c int unix_stream_listen(const char *path, · line 178
Quoted 5 lines
unix-socket.c +5
-0+if (hold_lock_file_for_update_timeout (&lock, path,0 , + opts->timeout_ms) <0 ) { +error_errno (_ ("could not lock listener socket '%s'" ), path); +return NULL ; + }Would you want to ping to see if it's alive before creating the lock? That would be the fast-path if we assume that a server will usually be there once started. Or is that supposed to happen in the caller (in which case I'd again wonder if this really should be happening in the simple-ipc code).
Quoted 4 lines
Starting a server should not happen that often, so I'm not sure it matters. And yes, a server once started should run for a long time. Pinging without the lock puts us back in another race, so we might as well lock first.
Definitely you need to ping under lock to avoid races. But I was thinking of an additional optimistic ping before we take the lock. I agree that starting the server should be rare, which is why I think there's value in seeing "is it up" before taking any lock.
But I suspect your thinking is that this ping happens in the caller anyway, before we hit any of this unix_socket_listen() code at all. And that makes sense to me. In fact, I guess it has to happen that way, because "try to connect" and "try to spin up a server" are likely happening in two separate processes entirely (we only spawn the second one if the first one failed its ping).
Quoted 12 lines
Quoted 11 lines
I'd think in those three cases you'd want:
- if lock contention, pause a moment and wait for the winner to spin up and serve requests
- if another server is live while we hold the lock, then we raced them and they won. Release the lock and start using them.
- if we really tried to call
unix_stream_listen()and that failed, give up now. There is some system error that is not likely to be fixed by trying anything more (e.g., ENAMETOOLONG).
Quoted 5 lines
Yes, I want to move the error messages out of these library layers.
And yes, if another server is running, our server instance should shutdown gracefully. Other client processes can just talk to them rather than us.
Right, that makes sense. Again, I was thinking earlier of the whole "try to connect, but spin up a server otherwise" thing happening in a single process. But by the time we get to the listen code, we have probably already spawned a server process, and have redirected its stderr somewhere. And likewise the caller doesn't even care that much if the server reports an error because it somebody else won the race. It only cares that after a few connect attempts it manages to talk to _somebody_.
Quoted 10 lines
Quoted 9 lines
unix-socket.c int unix_stream_listen(const char *path, · line 204
Quoted 1 line
unix-socket.c +1
-0+lstat (path, &server_socket->st_socket);This lstat I guess is part of your "periodically check to see if we're still the one holding the socket" strategy. We _shouldn't_ need that anymore, with the dotlocking, but I'm OK with it as a belt-and-suspenders check. But why are we filling in the lstat here? This seems like something that the unix-socket code doesn't really need to know about (though you do at least provide the complementary "
was_stolen" function here, so that part makes sense).
Quoted 7 lines
The dotlock is only on disk for the duration of the socket setup. We do the rollback (to delete the lockfile) once we have the socket open and ready for business.
The lstat gives me the inode of the socket on disk and we can watch it with future lstat's in the event loop and see if it changes and detect theft and auto-shutdown.
Right, I gradually came to the understanding of what your extra layer was trying to accomplish while reading (sometimes I'll go back and edit earlier comments in my review before sending out the mail, but in this case it seemed less confusing to leave my train of thought in place. That might not have been correct, though. ;) ).
I think if everybody is abiding by the lock system to create the socket, we probably don't strictly _need_ the theft detection. But it might not hurt as a belt-and-suspenders, or for cases where somebody thinks the socket is stale but it isn't (perhaps due to listen backlog or something while trying to do the connect() ping).
Quoted 21 lines
Quoted 12 lines
Quoted 12 lines
unix-socket.c +12
-0+void unix_stream_server__free ( +struct unix_stream_server_socket *server_socket) +{ +if (!server_socket) +return ; + +if (server_socket->fd_socket >=0 ) { +if (!unix_stream_server__was_stolen (server_socket)) +unlink (server_socket->path_socket); +close (server_socket->fd_socket); + } +Quoted 8 lines
unix-socket.c int unix_stream_listen(const char *path, · line 230
Quoted 3 lines
unix-socket.c +3
-0+free (server_socket->path_socket); +free (server_socket); +}OK, this makes sense. We only remove it if we're still the ones holding it. That's not done under lock, though, so it's possibly racy (somebody steals from us while _they_ hold the lock; we check and see "not stolen" right before they steal it, and then we unlink their stolen copy).
Quoted 8 lines
Right, I didn't bother with the lock here. I don't think we need it.
We technically still have the socket open and are listening on it when we lstat and unlink it. The other process should create the lock and try to connect. That should hang in the kernel because of the
accept()grace period. Then we close the socket and the client's connection request errors because we didn't accept it. They will see the error as no one is listening and then create their own socket.
I think there are still some races (at least if we believe that anything can be stolen in the first place). Something like:
- process A holds the socket but plans to exit
- process B takes the lock
- process B tries to ping us, but it doesn't work for some reason (this part is vague, but it's also the thing that makes stealing possible at all)
- process A calls was_stolen(), which says "no"
- process B decides nobody is there, so it unlinks the socket and creates its own
- process A calls unlink(), removing B's socket
A is OK with this; it was exiting anyway. But it just stranded B, who _thinks_ it owns the socket, but doesn't.
Again, there's a vagueness to "B somehow doesn't see A as listening" in the middle step. But without that step, I don't see how you'd really have stealing in the first place.
-Peff
Re: [PATCH v4 01/12] pkt-line: eliminate the need for static buffer in packet_write_gently()
In reply to Jeff HostetlerQuoted 11 lines · Jeff Hostetler
Quoted 8 lines · Junio C Hamano
I agree with both of you that the code is unnice in its stack usage and we want fix with
malloc(), or something like that, but sorry, I think I merged this round by mistake to 'next'.
As we won't be merging the topic to the upcoming release anyway, I am willing to revert the merge to 'next' and requeue an updated one, when it appears (I am also OK to see an incremental update, "oops, no, we realize we don't want to have it on the stack" fix-up, if this is the only glitch in the series that need to be fixed).
Quoted 4 lines
I'm preparing a follow-on patch series to address Peff's comments from Friday/Monday and yours from yesterday. I thought I'd send it as a set of new changes to sit on top of what we have in "next" if that would make things easier for you.
Yeah, that is OK, too. Sorry for the mistake of merging it too early.
Re: [PATCH v4 03/12] pkt-line: (optionally) libify the packet readers
In reply to Jeff KingQuoted 9 lines · Jeff King
I think the flag is doing sensible things; it's just that the word "never" in the name is confusing, since it is "never except this one time".
Would
PACKET_READ_GENTLE_ON_READ_ERRORbe a better name, to matchGENTLE_ON_EOF? I was tempted to just call it "ON_ERROR", since it also include parsing errors, but maybe somebody would think that includes ERR packets (that is more of a stretch, though, I think).
Quoted 4 lines
Likewise, I kind of wonder if callers would really prefer suppressing the
error()calls, too. Saying "error: the remote end hung up unexpectedly" is not that helpful if the "remote end" we are talking about is fsmonitor, and not the server side of a fetch.
Both sounds sensible.
Re: [PATCH v4 09/12] unix-socket: disallow chdir() when creating unix domain sockets
In reply to Jeff KingQuoted 19 lines · Jeff King
The feature is definitely useful; I think I did
1eb10f4091(unix-socket:
handle long socket pathnames, 2012-01-09) in response to a real problem.Certainly callers could handle the error themselves. The reason I pushed it down into the socket code was to avoid having to implement in multiple callers. There are only two, but we'd have needed it in both sides (credential-cache--daemon as the listener, and credential-cache as the client).
Ironically, the listening side now does a permanent
chdir()to the socket directory anyway, since6e61449051(credential-cache--daemon:
change to the socket dir on startup, 2016-02-23). So we could just do that first, and then feed the basename to the socket code.The client side would still need to handle it, though. It could probably also chdir to the socket directory without any real downside (once started, I don't think the helper program needs to access the filesystem at all outside of the socket).
Quoted 4 lines
So I dunno. I'd be OK to just rip the feature out in favor of doing those chdir()s. But that seems like a non-zero amount of work versus leaving, and the existing code has the benefit that if another caller shows up, it could benefit from the feature.
I am OK to keep the series as-is, and leave it to a possible future work to remove the need for chdir even for long paths and not having to return an error with ENAMETOOLONG; when such an update happens, the "fail if need to chdir" feature this patch is adding will become a no-op.
Re: [PATCH v4 09/12] unix-socket: disallow chdir() when creating unix domain sockets
Quoted 10 lines · Junio C Hamano
Quoted 4 lines
So I dunno. I'd be OK to just rip the feature out in favor of doing those chdir()s. But that seems like a non-zero amount of work versus leaving, and the existing code has the benefit that if another caller shows up, it could benefit from the feature.
I am OK to keep the series as-is, and leave it to a possible future work to remove the need for chdir even for long paths and not having to return an error with ENAMETOOLONG; when such an update happens, the "fail if need to chdir" feature this patch is adding will become a no-op.
For example, as this is UNIX-only codepath, I wonder if something like this would be a good way to avoid chdir() that would cause trouble.
- obtain a fd from socket(2)
- check if path is too long to fit in sa->sun_path
- if it does, bind(2) the fd to the address
- if it does not, fork(2) a child and
- in the child, chdir(2) there and use the shortened path to bind(2), and exit(3)
- the parents just wait(2)s for the child to return. By the time it dies, the fd would be successfully bound to the path.
- now we have a file descriptor that is bound at that path.
Re: [PATCH v4 09/12] unix-socket: disallow chdir() when creating unix domain sockets
Quoted 17 lines · Junio C Hamano
Quoted 10 lines · Junio C Hamano
Quoted 4 lines
So I dunno. I'd be OK to just rip the feature out in favor of doing those chdir()s. But that seems like a non-zero amount of work versus leaving, and the existing code has the benefit that if another caller shows up, it could benefit from the feature.
I am OK to keep the series as-is, and leave it to a possible future work to remove the need for chdir even for long paths and not having to return an error with ENAMETOOLONG; when such an update happens, the "fail if need to chdir" feature this patch is adding will become a no-op.
For example, as this is UNIX-only codepath, I wonder if something like this would be a good way to avoid
chdir()that would cause trouble.
Quoted 10 lines
- obtain a fd from socket(2)
- check if path is too long to fit insa->sun_path
- if it does, bind(2) the fd to the address
- if it does not, fork(2) a child and
- in the child, chdir(2) there and use the shortened path to bind(2), and exit(3)
- the parents just wait(2)s for the child to return. By the time it dies, the fd would be successfully bound to the path.
- now we have a file descriptor that is bound at that path.
If the trouble is that chdir() isn't thread-safe, I wonder if fork() creates its own headaches. :) I guess libc usually takes care of the basics with pthread_atfork(), etc, and the child otherwise would not need to access much data.
I don't know offhand if this trick actually works. I can imagine it does, but it hinges on the subtlety between an integer descriptor and the underlying "file description" (the term used in POSIX). Does binding a socket operate on the former (like close() does not close the parent's descriptor) or the latter (like lseek() impacts other descriptors).
I'd guess the latter, but I wasn't sure if you were suggesting this from experience or if you just invented the technique. ;)
-Peff
Re: [PATCH v4 09/12] unix-socket: disallow chdir() when creating unix domain sockets
Quoted 8 lines · Jeff King
I don't know offhand if this trick actually works. I can imagine it does, but it hinges on the subtlety between an integer descriptor and the underlying "file description" (the term used in POSIX). Does binding a socket operate on the former (like
close()does not close the parent's descriptor) or the latter (likelseek()impacts other descriptors).I'd guess the latter, but I wasn't sure if you were suggesting this from experience or if you just invented the technique. ;)
I was curious, but this does indeed work:
-- >8 --
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>
int main(void)
{
int listen_fd, client_fd;
struct addrinfo *ai;
pid_t pid;
getaddrinfo("127.0.0.1", "1234", NULL, &ai);
listen_fd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
pid = fork();
if (!pid) {
bind(listen_fd, ai->ai_addr, ai->ai_addrlen);
return 0;
}
waitpid(pid, NULL, 0);
listen(listen_fd, 5);
client_fd = accept(listen_fd, NULL, NULL);
write(client_fd, "foo\n", 4);
return 0;
}-- >8 --
-Peff
Re: [PATCH v4 09/12] unix-socket: disallow chdir() when creating unix domain sockets
Quoted 3 lines · Jeff King
Quoted 2 lines
I don't know offhand if this [bind in a child] trick actually works. ...
Quoted 1 line · Jeff King
I was curious, but this does indeed work:
[working example snipped]
Yes, it definitely works. The bind() call, on a Unix domain socket, creates a file system entity linked to the underlying socket instance. The file descriptors, in whatever processes have them, provide read/write/send/recv/etc linkage to the underlying socket instance (and also a refcount or other GC protection: with the ability to send sockets over sockets, simple refcounts stop working and we need real GC in the kernel...).
Of course, once all the file descriptor references are gone, the socket (eventually, depending on GC) evaporates. The file system entity does not count for keeping the underlying socket alive. At this point the file system entity is "dead". Unfortunately there's no way to test and clean out the dead entity atomically. The whole thing is kind of a mess.
Chris
Re: [PATCH v4 09/12] unix-socket: disallow chdir() when creating unix domain sockets
Quoted 16 lines · Chris Torek
Quoted 3 lines · Jeff King
Quoted 2 lines
I don't know offhand if this [bind in a child] trick actually works. ...
Quoted 1 line · Jeff King
I was curious, but this does indeed work:
[working example snipped]
Yes, it definitely works. The
bind()call, on a Unix domain socket, creates a file system entity linked to the underlying socket instance. The file descriptors, in whatever processes have them, provide read/write/send/recv/etc linkage to the underlying socket instance (and also a refcount or other GC protection: with the ability to send sockets over sockets, simple refcounts stop working and we need real GC in the kernel...).
Quoted 9 lines
Of course, once all the file descriptor references are gone, the socket (eventually, depending on GC) evaporates. The file system entity does not count for keeping the underlying socket alive. At this point the file system entity is "dead". Unfortunately there's no way to test and clean out the dead entity atomically. The whole thing is kind of a mess.
Chris
The original problem was that chdir() is not safe in a multi-threaded process because one thread calling chdir() will affect any concurrent file operations (open(), mkdir(), etc.) that use relative paths.
I think Adding a fork() at this layer would just create new types of problems. For example, if another thread was concurrently writing to a socket while we were setting up this new socket, we would suddenly
have 1 thread in each process now writing to that socket and the
receiver would get a mixture of output from both processes. Right?
Jeff
Re: [PATCH v4 09/12] unix-socket: disallow chdir() when creating unix domain sockets
Quoted 9 lines · Jeff Hostetler
The original problem was that
chdir()is not safe in a multi-threaded process because one thread callingchdir()will affect any concurrent file operations (open(),mkdir(), etc.) that use relative paths.I think Adding a
fork()at this layer would just create new types of problems. For example, if another thread was concurrently writing to a socket while we were setting up this new socket, we would suddenly have 1 thread in each process now writing to that socket and the receiver would get a mixture of output from both processes. Right?
cf. https://pubs.opengroup.org/onlinepubs/9699919799/functions/fork.html
The fork() function shall create a new process. The new process (child process) shall be an exact copy of the calling process (parent process) except as detailed below:
...
* A process shall be created with a single thread. If a multi-threaded process calls fork(), the new process shall contain a replica of the calling thread and its entire address space, possibly including the states of mutexes and other resources. Consequently, to avoid errors, the child process may only execute async-signal-safe operations until such time as one of the exec functions is called.
So, probably not.
Re: [PATCH v4 09/12] unix-socket: disallow chdir() when creating unix domain sockets
In reply to Junio C HamanoQuoted 26 lines · Junio C Hamano
Quoted 19 lines · Jeff King
The feature is definitely useful; I think I did
1eb10f4091(unix-socket:
handle long socket pathnames, 2012-01-09) in response to a real problem.Certainly callers could handle the error themselves. The reason I pushed it down into the socket code was to avoid having to implement in multiple callers. There are only two, but we'd have needed it in both sides (credential-cache--daemon as the listener, and credential-cache as the client).
Ironically, the listening side now does a permanent
chdir()to the socket directory anyway, since6e61449051(credential-cache--daemon:
change to the socket dir on startup, 2016-02-23). So we could just do that first, and then feed the basename to the socket code.The client side would still need to handle it, though. It could probably also chdir to the socket directory without any real downside (once started, I don't think the helper program needs to access the filesystem at all outside of the socket).
Quoted 4 lines
So I dunno. I'd be OK to just rip the feature out in favor of doing those chdir()s. But that seems like a non-zero amount of work versus leaving, and the existing code has the benefit that if another caller shows up, it could benefit from the feature.
Quoted 6 lines
I am OK to keep the series as-is, and leave it to a possible future work to remove the need for chdir even for long paths and not having to return an error with ENAMETOOLONG; when such an update happens, the "fail if need to chdir" feature this patch is adding will become a no-op.
I think I'd like to keep things as I have them now with the "disallow chdir()" option bit and save the "fork() / bind()" solution for a later patch series. Simple IPC is large enough as it is and the new ENAMETOOLONG error will only affect callers who set the bit. A later patch series can easily test and confirm the "fork() / bind() solution in isolation and test it on the other Unix hosts and then remove the bit from those callers (if we want).
Jeff
Re: [PATCH v4 09/12] unix-socket: disallow chdir() when creating unix domain sockets
Quoted 8 lines · Jeff Hostetler
Quoted 5 lines
I am OK to keep the series as-is, and leave it to a possible future work to remove the need for chdir even for long paths and not having to return an error with ENAMETOOLONG; when such an update happens, the "fail if need to chdir" feature this patch is adding will become a no-op.
I think I'd like to keep things as I have them now with the "disallow
chdir()" option bit
So we are on the same page.
Quoted 6 lines
and save the "
fork()/bind()" solution for a
later patch series. Simple IPC is large enough as it is and the new ENAMETOOLONG error will only affect callers who set the bit. A later patch series can easily test and confirm the "fork()/bind()solution in isolation and test it on the other Unix hosts and then remove the bit from those callers (if we want).
The bit will then become an unused API relic, but that is OK (I do not think fork/bind would be the best and/or only way to avoid chdir, though, but it won't matter in the context ofh tis discussion).
v5 cover Simple IPC Mechanism
In reply to Jeff Hostetler via GitGitGadgetHere is V5 of my "Simple IPC" series. This version is a reiteration of the series and combines the original "Simple IPC Mechanism V4" series [1] and the "Simple IPC Cleanups V1" series [2]. It squashes them together and then includes responses to last minute comments received on both.
These include: (a) Rename PACKET_READ_NEVER_DIE to PACKET_READ_GENTLE_ON_READ_ERROR. (b) Accept zero length timeout in unix_stream_socket__conect(). Only supply the default timeout when a negative value is passed. Make the default timeout value private to the .c file.
I apologize for rerolling something that is in "next". I think the combined result is better long term than preserving them as two sequential series. Given where we are in the release cycle, I thought it best to have a cleaned up series for consideration post v2.31.
[1] Upstream in jh/simple-ipc via
https://github.com/gitgitgadget/git/pull/766 (and in "next" relative to v2.30.1)
https://lore.kernel.org/git/[email protected]/T/#mbd1da5ff93ef273049090f697aeab68c74f698f1
[2] Upstream in `jh/simple-ipc-cleanups via
https://github.com/gitgitgadget/git/pull/893
https://lore.kernel.org/git/[email protected]/T/#t
Jeff Hostetler (9):
pkt-line: eliminate the need for static buffer in
packet_write_gently()
simple-ipc: design documentation for new IPC mechanism
simple-ipc: add win32 implementation
unix-socket: eliminate static unix_stream_socket() helper function
unix-socket: add backlog size option to unix_stream_listen()
unix-socket: disallow chdir() when creating unix domain sockets
unix-stream-server: create unix domain socket under lock
simple-ipc: add Unix domain socket implementation
t0052: add simple-ipc tests and t/helper/test-simple-ipc tool
Johannes Schindelin (3):
pkt-line: do not issue flush packets in write_packetized_*()
pkt-line: add PACKET_READ_GENTLE_ON_READ_ERROR option
pkt-line: add options argument to read_packetized_to_strbuf()
Documentation/technical/api-simple-ipc.txt | 105 +++ Makefile | 9 + builtin/credential-cache--daemon.c | 3 +- builtin/credential-cache.c | 2 +- compat/simple-ipc/ipc-shared.c | 28 + compat/simple-ipc/ipc-unix-socket.c | 986 +++++++++++++++++++++ compat/simple-ipc/ipc-win32.c | 751 ++++++++++++++++ config.mak.uname | 2 + contrib/buildsystems/CMakeLists.txt | 8 +- convert.c | 11 +- pkt-line.c | 58 +- pkt-line.h | 17 +- simple-ipc.h | 239 +++++ t/helper/test-simple-ipc.c | 787 ++++++++++++++++ t/helper/test-tool.c | 1 + t/helper/test-tool.h | 1 + t/t0052-simple-ipc.sh | 122 +++ unix-socket.c | 53 +- unix-socket.h | 12 +- unix-stream-server.c | 128 +++ unix-stream-server.h | 36 + 21 files changed, 3307 insertions(+), 52 deletions(-) create mode 100644 Documentation/technical/api-simple-ipc.txt create mode 100644 compat/simple-ipc/ipc-shared.c create mode 100644 compat/simple-ipc/ipc-unix-socket.c create mode 100644 compat/simple-ipc/ipc-win32.c create mode 100644 simple-ipc.h create mode 100644 t/helper/test-simple-ipc.c create mode 100755 t/t0052-simple-ipc.sh create mode 100644 unix-stream-server.c create mode 100644 unix-stream-server.h
base-commit: f01623b2c9d14207e497b21ebc6b3ec4afaf4b46
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-766%2Fjeffhostetler%2Fsimple-ipc-v5
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-766/jeffhostetler/simple-ipc-v5
Pull-Request: https://github.com/gitgitgadget/git/pull/766
Range-diff vs v4:
1: 2d6858b1625a ! 1: 311ea4a5cd71 pkt-line: eliminate the need for static buffer in packet_write_gently()
@@ Commit message
static buffer, thread-safe scratch space, or an excessively large stack
buffer.
- Change the API of `write_packetized_from_fd()` to accept a scratch space
- argument from its caller to avoid similar issues here.
+ Change `write_packetized_from_fd()` to allocate a temporary buffer rather
+ than using a static buffer to avoid similar issues here.
These changes are intended to make it easier to use pkt-line routines in
a multi-threaded context with multiple concurrent writers writing to
@@ Commit message
Signed-off-by: Jeff Hostetler <[email protected]>
- ## convert.c ##
-@@ convert.c: static int apply_multi_file_filter(const char *path, const char *src, size_t len
- if (err)
- goto done;
-
-- if (fd >= 0)
-- err = write_packetized_from_fd(fd, process->in);
-- else
-+ if (fd >= 0) {
-+ struct packet_scratch_space scratch;
-+ err = write_packetized_from_fd(fd, process->in, &scratch);
-+ } else
- err = write_packetized_from_buf(src, len, process->in);
- if (err)
- goto done;
-
## pkt-line.c ##
@@ pkt-line.c: int packet_write_fmt_gently(int fd, const char *fmt, ...)
@@ pkt-line.c: int packet_write_fmt_gently(int fd, const char *fmt, ...)
return 0;
}
@@ pkt-line.c: void packet_buf_write_len(struct strbuf *buf, const char *data, size_t len)
- packet_trace(data, len, 1);
- }
--int write_packetized_from_fd(int fd_in, int fd_out)
-+int write_packetized_from_fd(int fd_in, int fd_out,
-+ struct packet_scratch_space *scratch)
+ int write_packetized_from_fd(int fd_in, int fd_out)
{
- static char buf[LARGE_PACKET_DATA_MAX];
++ char *buf = xmalloc(LARGE_PACKET_DATA_MAX);
int err = 0;
ssize_t bytes_to_write;
while (!err) {
- bytes_to_write = xread(fd_in, buf, sizeof(buf));
-+ bytes_to_write = xread(fd_in, scratch->buffer,
-+ sizeof(scratch->buffer));
- if (bytes_to_write < 0)
+- if (bytes_to_write < 0)
++ bytes_to_write = xread(fd_in, buf, LARGE_PACKET_DATA_MAX);
++ if (bytes_to_write < 0) {
++ free(buf);
return COPY_READ_ERROR;
++ }
if (bytes_to_write == 0)
break;
-- err = packet_write_gently(fd_out, buf, bytes_to_write);
-+ err = packet_write_gently(fd_out, scratch->buffer,
-+ bytes_to_write);
+ err = packet_write_gently(fd_out, buf, bytes_to_write);
}
if (!err)
err = packet_flush_gently(fd_out);
-
- ## pkt-line.h ##
-@@
- #include "strbuf.h"
- #include "sideband.h"
-
-+#define LARGE_PACKET_MAX 65520
-+#define LARGE_PACKET_DATA_MAX (LARGE_PACKET_MAX - 4)
-+
-+struct packet_scratch_space {
-+ char buffer[LARGE_PACKET_DATA_MAX]; /* does not include header bytes */
-+};
-+
- /*
- * Write a packetized stream, where each line is preceded by
- * its length (including the header) as a 4-byte hex number.
-@@ pkt-line.h: void packet_buf_write(struct strbuf *buf, const char *fmt, ...) __attribute__((f
- void packet_buf_write_len(struct strbuf *buf, const char *data, size_t len);
- int packet_flush_gently(int fd);
- int packet_write_fmt_gently(int fd, const char *fmt, ...) __attribute__((format (printf, 2, 3)));
--int write_packetized_from_fd(int fd_in, int fd_out);
-+int write_packetized_from_fd(int fd_in, int fd_out, struct packet_scratch_space *scratch);
- int write_packetized_from_buf(const char *src_in, size_t len, int fd_out);
-
- /*
-@@ pkt-line.h: enum packet_read_status packet_reader_read(struct packet_reader *reader);
- enum packet_read_status packet_reader_peek(struct packet_reader *reader);
-
- #define DEFAULT_PACKET_MAX 1000
--#define LARGE_PACKET_MAX 65520
--#define LARGE_PACKET_DATA_MAX (LARGE_PACKET_MAX - 4)
-+
- extern char packet_buffer[LARGE_PACKET_MAX];
++ free(buf);
+ return err;
+ }
- struct packet_writer {
2: 91a9f63d6692 ! 2: 25157c1f4873 pkt-line: do not issue flush packets in write_packetized_*()
@@ Commit message
## convert.c ##
@@ convert.c: static int apply_multi_file_filter(const char *path, const char *src, size_t len
+ goto done;
- if (fd >= 0) {
- struct packet_scratch_space scratch;
-- err = write_packetized_from_fd(fd, process->in, &scratch);
-+ err = write_packetized_from_fd_no_flush(fd, process->in, &scratch);
- } else
+ if (fd >= 0)
+- err = write_packetized_from_fd(fd, process->in);
++ err = write_packetized_from_fd_no_flush(fd, process->in);
+ else
- err = write_packetized_from_buf(src, len, process->in);
+ err = write_packetized_from_buf_no_flush(src, len, process->in);
+ if (err)
@@ pkt-line.c: void packet_buf_write_len(struct strbuf *buf, const char *data, size
packet_trace(data, len, 1);
}
--int write_packetized_from_fd(int fd_in, int fd_out,
-- struct packet_scratch_space *scratch)
-+int write_packetized_from_fd_no_flush(int fd_in, int fd_out,
-+ struct packet_scratch_space *scratch)
+-int write_packetized_from_fd(int fd_in, int fd_out)
++int write_packetized_from_fd_no_flush(int fd_in, int fd_out)
{
+ char *buf = xmalloc(LARGE_PACKET_DATA_MAX);
int err = 0;
- ssize_t bytes_to_write;
-@@ pkt-line.c: int write_packetized_from_fd(int fd_in, int fd_out,
- err = packet_write_gently(fd_out, scratch->buffer,
- bytes_to_write);
+@@ pkt-line.c: int write_packetized_from_fd(int fd_in, int fd_out)
+ break;
+ err = packet_write_gently(fd_out, buf, bytes_to_write);
}
- if (!err)
- err = packet_flush_gently(fd_out);
+ free(buf);
return err;
}
@@ pkt-line.h: void packet_buf_write(struct strbuf *buf, const char *fmt, ...) __at
void packet_buf_write_len(struct strbuf *buf, const char *data, size_t len);
int packet_flush_gently(int fd);
int packet_write_fmt_gently(int fd, const char *fmt, ...) __attribute__((format (printf, 2, 3)));
--int write_packetized_from_fd(int fd_in, int fd_out, struct packet_scratch_space *scratch);
+-int write_packetized_from_fd(int fd_in, int fd_out);
-int write_packetized_from_buf(const char *src_in, size_t len, int fd_out);
-+int write_packetized_from_fd_no_flush(int fd_in, int fd_out, struct packet_scratch_space *scratch);
++int write_packetized_from_fd_no_flush(int fd_in, int fd_out);
+int write_packetized_from_buf_no_flush(const char *src_in, size_t len, int fd_out);
/* 3: e05467def4e1 ! 3: af3d13113bc9 pkt-line: (optionally) libify the packet readers
@@ Metadata
Author: Johannes Schindelin <[email protected]>
## Commit message ##
- pkt-line: (optionally) libify the packet readers
+ pkt-line: add PACKET_READ_GENTLE_ON_READ_ERROR option
+
+ Introduce PACKET_READ_GENTLE_ON_READ_ERROR option to help libify the
+ packet readers.
So far, the (possibly indirect) callers of get_packet_data() can ask that function to return an error instead of die()ing upon end-of-file. However, random read errors will still cause the process to die.
So let's introduce an explicit option to tell the packet reader
- machinery to please be nice and only return an error.
+ machinery to please be nice and only return an error on read errors.
This change prepares pkt-line for use by long-running daemon processes. Such processes should be able to serve multiple concurrent clients and
@@ Commit message
a daemon should be able to drop that connection and continue serving existing and future connections.
- This ability will be used by a Git-aware "Internal FSMonitor" feature
+ This ability will be used by a Git-aware "Builtin FSMonitor" feature
in a later patch series.
+ Signed-off-by: Jeff Hostetler <[email protected]>
## pkt-line.c ##
@@ pkt-line.c: static int get_packet_data(int fd, char **src_buf, size_t *src_size,
@@ pkt-line.c: static int get_packet_data(int fd, char **src_buf, size_t *src_size,
ret = read_in_full(fd, dst, size);
- if (ret < 0)
+ if (ret < 0) {
-+ if (options & PACKET_READ_NEVER_DIE)
++ if (options & PACKET_READ_GENTLE_ON_READ_ERROR)
+ return error_errno(_("read error"));
die_errno(_("read error"));
+ }
@@ pkt-line.c: static int get_packet_data(int fd, char **src_buf, size_t *src_size,
if (options & PACKET_READ_GENTLE_ON_EOF)
return -1;
-+ if (options & PACKET_READ_NEVER_DIE)
++ if (options & PACKET_READ_GENTLE_ON_READ_ERROR)
+ return error(_("the remote end hung up unexpectedly"));
die(_("the remote end hung up unexpectedly"));
}
@@ pkt-line.c: enum packet_read_status packet_read_with_status(int fd, char **src_b
len = packet_length(linelen);
if (len < 0) {
-+ if (options & PACKET_READ_NEVER_DIE)
++ if (options & PACKET_READ_GENTLE_ON_READ_ERROR)
+ return error(_("protocol error: bad line length "
+ "character: %.4s"), linelen);
die(_("protocol error: bad line length character: %.4s"), linelen);
@@ pkt-line.c: enum packet_read_status packet_read_with_status(int fd, char **src_b
*pktlen = 0;
return PACKET_READ_RESPONSE_END;
} else if (len < 4) {
-+ if (options & PACKET_READ_NEVER_DIE)
++ if (options & PACKET_READ_GENTLE_ON_READ_ERROR)
+ return error(_("protocol error: bad line length %d"),
+ len);
die(_("protocol error: bad line length %d"), len);
@@ pkt-line.c: enum packet_read_status packet_read_with_status(int fd, char **src_b
len -= 4;
- if ((unsigned)len >= size)
+ if ((unsigned)len >= size) {
-+ if (options & PACKET_READ_NEVER_DIE)
++ if (options & PACKET_READ_GENTLE_ON_READ_ERROR)
+ return error(_("protocol error: bad line length %d"),
+ len);
die(_("protocol error: bad line length %d"), len);
@@ pkt-line.h: int write_packetized_from_buf_no_flush(const char *src_in, size_t le
* If options contains PACKET_READ_DIE_ON_ERR_PACKET, it dies when it sees an
* ERR packet.
+ *
-+ * With PACKET_READ_NEVER_DIE, no errors are allowed to trigger die() (except -+ * an ERR packet, when PACKET_READ_DIE_ON_ERR_PACKET is in effect). ++ * If options contains PACKET_READ_GENTLE_ON_READ_ERROR, we will not die ++ * on read errors, but instead return -1. However, we may still die on an ++ * ERR packet (if requested).
*/
- #define PACKET_READ_GENTLE_ON_EOF (1u<<0)
- #define PACKET_READ_CHOMP_NEWLINE (1u<<1)
- #define PACKET_READ_DIE_ON_ERR_PACKET (1u<<2)
-+#define PACKET_READ_NEVER_DIE (1u<<3)
+-#define PACKET_READ_GENTLE_ON_EOF (1u<<0)
+-#define PACKET_READ_CHOMP_NEWLINE (1u<<1)
+-#define PACKET_READ_DIE_ON_ERR_PACKET (1u<<2)
++#define PACKET_READ_GENTLE_ON_EOF (1u<<0)
++#define PACKET_READ_CHOMP_NEWLINE (1u<<1)
++#define PACKET_READ_DIE_ON_ERR_PACKET (1u<<2)
++#define PACKET_READ_GENTLE_ON_READ_ERROR (1u<<3)
int packet_read(int fd, char **src_buffer, size_t *src_len, char
*buffer, unsigned size, int options);
4: 81e14bed955c = 4: b73e66a69b61 pkt-line: add options argument to read_packetized_to_strbuf()
5: 22eec60761a8 < -: ------------ simple-ipc: design documentation for new IPC mechanism
-: ------------ > 5: 1ae99d824a21 simple-ipc: design documentation for new IPC mechanism
6: 171ec43ecfa4 ! 6: 8b3ce40e4538 simple-ipc: add win32 implementation
@@ compat/simple-ipc/ipc-win32.c (new)
+
+ if (read_packetized_to_strbuf(
+ connection->fd, answer,
-+ PACKET_READ_GENTLE_ON_EOF | PACKET_READ_NEVER_DIE) < 0) {
++ PACKET_READ_GENTLE_ON_EOF | PACKET_READ_GENTLE_ON_READ_ERROR) < 0) {
+ ret = error(_("could not read IPC response"));
+ goto done;
+ }
@@ compat/simple-ipc/ipc-win32.c (new)
+
+ ret = read_packetized_to_strbuf(
+ reply_data.fd, &buf,
-+ PACKET_READ_GENTLE_ON_EOF | PACKET_READ_NEVER_DIE);
++ PACKET_READ_GENTLE_ON_EOF | PACKET_READ_GENTLE_ON_READ_ERROR);
+ if (ret >= 0) {
+ ret = server_thread_data->server_data->application_cb(
+ server_thread_data->server_data->application_data,
@@ compat/simple-ipc/ipc-win32.c (new)
+ *returned_server_data = NULL;
+
+ ret = initialize_pipe_name(path, wpath, ARRAY_SIZE(wpath));
-+ if (ret < 0)
-+ return error(
-+ _("could not create normalized wchar_t path for '%s'"),
-+ path);
++ if (ret < 0) {
++ errno = EINVAL;
++ return -1;
++ }
+
+ hPipeFirst = create_new_pipe(wpath, 1);
-+ if (hPipeFirst == INVALID_HANDLE_VALUE)
-+ return error(_("IPC server already running on '%s'"), path);
++ if (hPipeFirst == INVALID_HANDLE_VALUE) {
++ errno = EADDRINUSE;
++ return -2;
++ }
+
+ server_data = xcalloc(1, sizeof(*server_data));
+ server_data->magic = MAGIC_SERVER_DATA;
@@ simple-ipc.h (new)
+ *
+ * Returns 0 if the asynchronous server pool was started successfully.
+ * Returns -1 if not.
++ * Returns -2 if we could not startup because another server is using
++ * the socket or named pipe.
+ *
+ * When a client IPC message is received, the `application_cb` will be
+ * called (possibly on a random thread) to handle the message and
@@ simple-ipc.h (new)
+ *
+ * Returns 0 after the server has completed successfully.
+ * Returns -1 if the server cannot be started.
++ * Returns -2 if we could not startup because another server is using
++ * the socket or named pipe.
+ *
+ * When a client IPC message is received, the `application_cb` will be
+ * called (possibly on a random thread) to handle the message and 7: b368318e6a23 ! 7: 34df1af98e5b unix-socket: elimiate static unix_stream_socket() helper function
@@ Metadata
Author: Jeff Hostetler <[email protected]>
## Commit message ##
- unix-socket: elimiate static unix_stream_socket() helper function
+ unix-socket: eliminate static unix_stream_socket() helper function
The static helper function unix_stream_socket() calls die(). This
is not appropriate for all callers. Eliminate the wrapper function
8: 985b2e02b2df ! 8: d6ff6e0e050a unix-socket: add backlog size option to unix_stream_listen()
@@ builtin/credential-cache--daemon.c: static int serve_cache_loop(int fd)
## unix-socket.c ##
+@@
+ #include "cache.h"
+ #include "unix-socket.h"
+
++#define DEFAULT_UNIX_STREAM_LISTEN_BACKLOG (5)
++
+ static int chdir_len(const char *orig, int len)
+ {
+ char *path = xmemdupz(orig, len);
@@ unix-socket.c: int unix_stream_connect(const char *path)
return -1;
}
@@ unix-socket.h
+ int listen_backlog_size;
+};
+
-+#define DEFAULT_UNIX_STREAM_LISTEN_BACKLOG (5)
-+
-+#define UNIX_STREAM_LISTEN_OPTS_INIT \
-+{ \
-+ .listen_backlog_size = DEFAULT_UNIX_STREAM_LISTEN_BACKLOG, \
-+}
++#define UNIX_STREAM_LISTEN_OPTS_INIT { 0 }
+
int unix_stream_connect(const char *path);
-int unix_stream_listen(const char *path); 9: 1bfa36409d07 ! 9: 21b8d3c63dbf unix-socket: disallow chdir() when creating unix domain sockets
@@ unix-socket.h
+ unsigned int disallow_chdir:1;
};
- #define DEFAULT_UNIX_STREAM_LISTEN_BACKLOG (5) -@@ unix-socket.h: struct unix_stream_listen_opts {
- #define UNIX_STREAM_LISTEN_OPTS_INIT \
- { \
- .listen_backlog_size = DEFAULT_UNIX_STREAM_LISTEN_BACKLOG, \
-+ .disallow_chdir = 0, \
- }
+ #define UNIX_STREAM_LISTEN_OPTS_INIT { 0 }
-int unix_stream_connect(const char *path);
+int unix_stream_connect(const char *path, int disallow_chdir);
10: b443e11ac32f ! 10: 1ee9de55a106 unix-socket: create unix_stream_server__listen_with_lock()
@@ Metadata
Author: Jeff Hostetler <[email protected]>
## Commit message ##
- unix-socket: create `unix_stream_server__listen_with_lock()`
+ unix-stream-server: create unix domain socket under lock
- Create a version of `unix_stream_listen()` that uses a ".lock" lockfile
- to create the unix domain socket in a race-free manner.
+ Create a wrapper class for `unix_stream_listen()` that uses a ".lock"
+ lockfile to create the unix domain socket in a race-free manner.
Unix domain sockets have a fundamental problem on Unix systems because
they persist in the filesystem until they are deleted. This is
@@ Commit message
As an alternative, we hold a plain lockfile ("<path>.lock") as a
mutual exclusion device. Under the lock, we test if an existing
- socket ("<path>") is has an active server. If not, create a new
- socket and begin listening. Then we rollback the lockfile in all
- cases.
+ socket ("<path>") is has an active server. If not, we create a new
+ socket and begin listening. Then we use "rollback" to delete the
+ lockfile in all cases.
+
+ This wrapper code conceptually exists at a higher-level than the core
+ unix_stream_connect() and unix_stream_listen() routines that it
+ consumes. It is isolated in a wrapper class for clarity.
Signed-off-by: Jeff Hostetler <[email protected]>
- ## unix-socket.c ##
+ ## Makefile ##
+@@ Makefile: ifdef NO_UNIX_SOCKETS
+ BASIC_CFLAGS += -DNO_UNIX_SOCKETS
+ else
+ LIB_OBJS += unix-socket.o
++ LIB_OBJS += unix-stream-server.o
+ endif
+
+ ifdef USE_WIN32_IPC
+
+ ## contrib/buildsystems/CMakeLists.txt ##
+@@ contrib/buildsystems/CMakeLists.txt: if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
+
+ elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux")
+ add_compile_definitions(PROCFS_EXECUTABLE_PATH="/proc/self/exe" HAVE_DEV_TTY )
+- list(APPEND compat_SOURCES unix-socket.c)
++ list(APPEND compat_SOURCES unix-socket.c unix-stream-server.c)
+ endif()
+
+ if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
+
+ ## unix-stream-server.c (new) ##
@@
- #include "cache.h"
++#include "cache.h"
+#include "lockfile.h"
- #include "unix-socket.h"
-
- static int chdir_len(const char *orig, int len)
-@@ unix-socket.c: int unix_stream_listen(const char *path,
- errno = saved_errno;
- return -1;
- }
++#include "unix-socket.h"
++#include "unix-stream-server.h"
+
++#define DEFAULT_LOCK_TIMEOUT (100)
++
++/*
++ * Try to connect to a unix domain socket at `path` (if it exists) and
++ * see if there is a server listening.
++ *
++ * We don't know if the socket exists, whether a server died and
++ * failed to cleanup, or whether we have a live server listening, so
++ * we "poke" it.
++ *
++ * We immediately hangup without sending/receiving any data because we
++ * don't know anything about the protocol spoken and don't want to
++ * block while writing/reading data. It is sufficient to just know
++ * that someone is listening.
++ */
+static int is_another_server_alive(const char *path,
+ const struct unix_stream_listen_opts *opts)
+{
-+ struct stat st;
-+ int fd;
-+
-+ if (!lstat(path, &st) && S_ISSOCK(st.st_mode)) {
-+ /*
-+ * A socket-inode exists on disk at `path`, but we
-+ * don't know whether it belongs to an active server
-+ * or whether the last server died without cleaning
-+ * up.
-+ *
-+ * Poke it with a trivial connection to try to find
-+ * out.
-+ */
-+ fd = unix_stream_connect(path, opts->disallow_chdir);
-+ if (fd >= 0) {
-+ close(fd);
-+ return 1;
-+ }
++ int fd = unix_stream_connect(path, opts->disallow_chdir);
++ if (fd >= 0) {
++ close(fd);
++ return 1;
+ }
+
+ return 0;
+}
+
-+struct unix_stream_server_socket *unix_stream_server__listen_with_lock(
++int unix_stream_server__create(
+ const char *path,
-+ const struct unix_stream_listen_opts *opts)
++ const struct unix_stream_listen_opts *opts,
++ long timeout_ms,
++ struct unix_stream_server_socket **new_server_socket)
+{
+ struct lock_file lock = LOCK_INIT;
+ int fd_socket;
+ struct unix_stream_server_socket *server_socket;
+
++ *new_server_socket = NULL;
++
++ if (timeout_ms < 0)
++ timeout_ms = DEFAULT_LOCK_TIMEOUT;
++
+ /*
+ * Create a lock at "<path>.lock" if we can.
+ */
-+ if (hold_lock_file_for_update_timeout(&lock, path, 0,
-+ opts->timeout_ms) < 0) {
-+ error_errno(_("could not lock listener socket '%s'"), path);
-+ return NULL;
-+ }
++ if (hold_lock_file_for_update_timeout(&lock, path, 0, timeout_ms) < 0)
++ return -1;
+
+ /*
+ * If another server is listening on "<path>" give up. We do not
+ * want to create a socket and steal future connections from them.
+ */
+ if (is_another_server_alive(path, opts)) {
-+ errno = EADDRINUSE;
-+ error_errno(_("listener socket already in use '%s'"), path);
+ rollback_lock_file(&lock);
-+ return NULL;
++ errno = EADDRINUSE;
++ return -2;
+ }
+
+ /*
@@ unix-socket.c: int unix_stream_listen(const char *path,
+ */
+ fd_socket = unix_stream_listen(path, opts);
+ if (fd_socket < 0) {
-+ error_errno(_("could not create listener socket '%s'"), path);
++ int saved_errno = errno;
+ rollback_lock_file(&lock);
-+ return NULL;
++ errno = saved_errno;
++ return -1;
+ }
+
+ server_socket = xcalloc(1, sizeof(*server_socket));
@@ unix-socket.c: int unix_stream_listen(const char *path,
+ server_socket->fd_socket = fd_socket;
+ lstat(path, &server_socket->st_socket);
+
++ *new_server_socket = server_socket;
++
+ /*
+ * Always rollback (just delete) "<path>.lock" because we already created
+ * "<path>" as a socket and do not want to commit_lock to do the atomic
@@ unix-socket.c: int unix_stream_listen(const char *path,
+ */
+ rollback_lock_file(&lock);
+
-+ return server_socket;
++ return 0;
+}
+
+void unix_stream_server__free(
@@ unix-socket.c: int unix_stream_listen(const char *path,
+
+ if (st_now.st_ino != server_socket->st_socket.st_ino)
+ return 1;
++ if (st_now.st_dev != server_socket->st_socket.st_dev)
++ return 1;
+
-+ /* We might also consider the ctime on some platforms. */
++ if (!S_ISSOCK(st_now.st_mode))
++ return 1;
+
+ return 0;
+}
- ## unix-socket.h ##
+ ## unix-stream-server.h (new) ##
@@
- #define UNIX_SOCKET_H
-
- struct unix_stream_listen_opts {
-+ long timeout_ms;
- int listen_backlog_size;
- unsigned int disallow_chdir:1;
- };
-
-+#define DEFAULT_UNIX_STREAM_LISTEN_TIMEOUT (100)
- #define DEFAULT_UNIX_STREAM_LISTEN_BACKLOG (5)
-
- #define UNIX_STREAM_LISTEN_OPTS_INIT \
- { \
-+ .timeout_ms = DEFAULT_UNIX_STREAM_LISTEN_TIMEOUT, \
- .listen_backlog_size = DEFAULT_UNIX_STREAM_LISTEN_BACKLOG, \
- .disallow_chdir = 0, \
- }
-@@ unix-socket.h: int unix_stream_connect(const char *path, int disallow_chdir);
- int unix_stream_listen(const char *path,
- const struct unix_stream_listen_opts *opts);
-
++#ifndef UNIX_STREAM_SERVER_H
++#define UNIX_STREAM_SERVER_H
++
++#include "unix-socket.h"
++
+struct unix_stream_server_socket {
+ char *path_socket;
+ struct stat st_socket;
@@ unix-socket.h: int unix_stream_connect(const char *path, int disallow_chdir);
+/*
+ * Create a Unix Domain Socket at the given path under the protection
+ * of a '.lock' lockfile.
++ *
++ * Returns 0 on success, -1 on error, -2 if socket is in use.
+ */
-+struct unix_stream_server_socket *unix_stream_server__listen_with_lock(
++int unix_stream_server__create(
+ const char *path,
-+ const struct unix_stream_listen_opts *opts);
++ const struct unix_stream_listen_opts *opts,
++ long timeout_ms,
++ struct unix_stream_server_socket **server_socket);
+
+/*
+ * Close and delete the socket.
@@ unix-socket.h: int unix_stream_connect(const char *path, int disallow_chdir);
+int unix_stream_server__was_stolen(
+ struct unix_stream_server_socket *server_socket);
+
- #endif /* UNIX_SOCKET_H */
++#endif /* UNIX_STREAM_SERVER_H */
11: 43c8db9a4468 ! 11: f2e3b046cc8f simple-ipc: add Unix domain socket implementation
@@ Commit message
## Makefile ##
@@ Makefile: ifdef NO_UNIX_SOCKETS
- BASIC_CFLAGS += -DNO_UNIX_SOCKETS
else
LIB_OBJS += unix-socket.o
+ LIB_OBJS += unix-stream-server.o
+ LIB_OBJS += compat/simple-ipc/ipc-shared.o
+ LIB_OBJS += compat/simple-ipc/ipc-unix-socket.o
endif
@@ compat/simple-ipc/ipc-unix-socket.c (new)
+#include "pkt-line.h"
+#include "thread-utils.h"
+#include "unix-socket.h"
++#include "unix-stream-server.h"
+
+#ifdef NO_UNIX_SOCKETS
+#error compat/simple-ipc/ipc-unix-socket.c requires Unix sockets
@@ compat/simple-ipc/ipc-unix-socket.c (new)
+
+ if (read_packetized_to_strbuf(
+ connection->fd, answer,
-+ PACKET_READ_GENTLE_ON_EOF | PACKET_READ_NEVER_DIE) < 0) {
++ PACKET_READ_GENTLE_ON_EOF | PACKET_READ_GENTLE_ON_READ_ERROR) < 0) {
+ ret = error(_("could not read IPC response"));
+ goto done;
+ }
@@ compat/simple-ipc/ipc-unix-socket.c (new)
+
+ ret = read_packetized_to_strbuf(
+ reply_data.fd, &buf,
-+ PACKET_READ_GENTLE_ON_EOF | PACKET_READ_NEVER_DIE);
++ PACKET_READ_GENTLE_ON_EOF | PACKET_READ_GENTLE_ON_READ_ERROR);
+ if (ret >= 0) {
+ ret = worker_thread_data->server_data->application_cb(
+ worker_thread_data->server_data->application_data,
@@ compat/simple-ipc/ipc-unix-socket.c (new)
+ */
+#define LISTEN_BACKLOG (50)
+
-+static struct unix_stream_server_socket *create_listener_socket(
++static int create_listener_socket(
+ const char *path,
-+ const struct ipc_server_opts *ipc_opts)
++ const struct ipc_server_opts *ipc_opts,
++ struct unix_stream_server_socket **new_server_socket)
+{
+ struct unix_stream_server_socket *server_socket = NULL;
+ struct unix_stream_listen_opts uslg_opts = UNIX_STREAM_LISTEN_OPTS_INIT;
++ int ret;
+
+ uslg_opts.listen_backlog_size = LISTEN_BACKLOG;
+ uslg_opts.disallow_chdir = ipc_opts->uds_disallow_chdir;
+
-+ server_socket = unix_stream_server__listen_with_lock(path, &uslg_opts);
-+ if (!server_socket)
-+ return NULL;
++ ret = unix_stream_server__create(path, &uslg_opts, -1, &server_socket);
++ if (ret)
++ return ret;
+
+ if (set_socket_blocking_flag(server_socket->fd_socket, 1)) {
+ int saved_errno = errno;
-+ error_errno(_("could not set listener socket nonblocking '%s'"),
-+ path);
+ unix_stream_server__free(server_socket);
+ errno = saved_errno;
-+ return NULL;
++ return -1;
+ }
+
++ *new_server_socket = server_socket;
++
+ trace2_data_string("ipc-server", NULL, "listen-with-lock", path);
-+ return server_socket;
++ return 0;
+}
+
-+static struct unix_stream_server_socket *setup_listener_socket(
++static int setup_listener_socket(
+ const char *path,
-+ const struct ipc_server_opts *ipc_opts)
++ const struct ipc_server_opts *ipc_opts,
++ struct unix_stream_server_socket **new_server_socket)
+{
-+ struct unix_stream_server_socket *server_socket;
++ int ret, saved_errno;
+
+ trace2_region_enter("ipc-server", "create-listener_socket", NULL);
-+ server_socket = create_listener_socket(path, ipc_opts);
++
++ ret = create_listener_socket(path, ipc_opts, new_server_socket);
++
++ saved_errno = errno;
+ trace2_region_leave("ipc-server", "create-listener_socket", NULL);
++ errno = saved_errno;
+
-+ return server_socket;
++ return ret;
+}
+
+/*
@@ compat/simple-ipc/ipc-unix-socket.c (new)
+ struct ipc_server_data *server_data;
+ int sv[2];
+ int k;
++ int ret;
+ int nr_threads = opts->nr_threads;
+
+ *returned_server_data = NULL;
@@ compat/simple-ipc/ipc-unix-socket.c (new)
+ * connection or a shutdown request without spinning.
+ */
+ if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) < 0)
-+ return error_errno(_("could not create socketpair for '%s'"),
-+ path);
++ return -1;
+
+ if (set_socket_blocking_flag(sv[1], 1)) {
+ int saved_errno = errno;
+ close(sv[0]);
+ close(sv[1]);
+ errno = saved_errno;
-+ return error_errno(_("making socketpair nonblocking '%s'"),
-+ path);
++ return -1;
+ }
+
-+ server_socket = setup_listener_socket(path, opts);
-+ if (!server_socket) {
++ ret = setup_listener_socket(path, opts, &server_socket);
++ if (ret) {
+ int saved_errno = errno;
+ close(sv[0]);
+ close(sv[1]);
+ errno = saved_errno;
-+ return -1;
++ return ret;
+ }
+
+ server_data = xcalloc(1, sizeof(*server_data));
12: 09568a6500dd ! 12: 6ccc7472096f t0052: add simple-ipc tests and t/helper/test-simple-ipc tool
@@ t/helper/test-simple-ipc.c (new)
+ return app__unhandled_command(command, reply_cb, reply_data);
+}
+
++struct cl_args
++{
++ const char *subcommand;
++ const char *path;
++ const char *token;
++
++ int nr_threads;
++ int max_wait_sec;
++ int bytecount;
++ int batchsize;
++
++ char bytevalue;
++};
++
++static struct cl_args cl_args = {
++ .subcommand = NULL,
++ .path = "ipc-test",
++ .token = NULL,
++
++ .nr_threads = 5,
++ .max_wait_sec = 60,
++ .bytecount = 1024,
++ .batchsize = 10,
++
++ .bytevalue = 'x',
++};
++
+/*
+ * This process will run as a simple-ipc server and listen for IPC commands
+ * from client processes.
+ */
-+static int daemon__run_server(const char *path, int argc, const char **argv)
++static int daemon__run_server(void)
+{
-+ struct ipc_server_opts opts = {
-+ .nr_threads = 5
-+ };
++ int ret;
+
-+ const char * const daemon_usage[] = {
-+ N_("test-helper simple-ipc run-daemon [<options>"),
-+ NULL
-+ };
-+ struct option daemon_options[] = {
-+ OPT_INTEGER(0, "threads", &opts.nr_threads,
-+ N_("number of threads in server thread pool")),
-+ OPT_END()
++ struct ipc_server_opts opts = {
++ .nr_threads = cl_args.nr_threads,
+ };
+
-+ argc = parse_options(argc, argv, NULL, daemon_options, daemon_usage, 0);
-+
-+ if (opts.nr_threads < 1)
-+ opts.nr_threads = 1;
-+
+ /*
+ * Synchronously run the ipc-server. We don't need any application
+ * instance data, so pass an arbitrary pointer (that we'll later
+ * verify made the round trip).
+ */
-+ return ipc_server_run(path, &opts, test_app_cb, (void*)&my_app_data);
++ ret = ipc_server_run(cl_args.path, &opts, test_app_cb, (void*)&my_app_data);
++ if (ret == -2)
++ error(_("socket/pipe already in use: '%s'"), cl_args.path);
++ else if (ret == -1)
++ error_errno(_("could not start server on: '%s'"), cl_args.path);
++
++ return ret;
+}
+
+#ifndef GIT_WINDOWS_NATIVE
@@ t/helper/test-simple-ipc.c (new)
+ * This is adapted from `daemonize()`. Use `fork()` to directly create and
+ * run the daemon in a child process.
+ */
-+static int spawn_server(const char *path,
-+ const struct ipc_server_opts *opts,
-+ pid_t *pid)
++static int spawn_server(pid_t *pid)
+{
++ struct ipc_server_opts opts = {
++ .nr_threads = cl_args.nr_threads,
++ };
++
+ *pid = fork();
+
+ switch (*pid) {
@@ t/helper/test-simple-ipc.c (new)
+ close(2);
+ sanitize_stdfds();
+
-+ return ipc_server_run(path, opts, test_app_cb, (void*)&my_app_data);
++ return ipc_server_run(cl_args.path, &opts, test_app_cb,
++ (void*)&my_app_data);
+
+ case -1:
+ return error_errno(_("could not spawn daemon in the background"));
@@ t/helper/test-simple-ipc.c (new)
+ * have `fork(2)`. Spawn a normal Windows child process but without the
+ * limitations of `start_command()` and `finish_command()`.
+ */
-+static int spawn_server(const char *path,
-+ const struct ipc_server_opts *opts,
-+ pid_t *pid)
++static int spawn_server(pid_t *pid)
+{
+ char test_tool_exe[MAX_PATH];
+ struct strvec args = STRVEC_INIT;
@@ t/helper/test-simple-ipc.c (new)
+ strvec_push(&args, test_tool_exe);
+ strvec_push(&args, "simple-ipc");
+ strvec_push(&args, "run-daemon");
-+ strvec_pushf(&args, "--threads=%d", opts->nr_threads);
++ strvec_pushf(&args, "--name=%s", cl_args.path);
++ strvec_pushf(&args, "--threads=%d", cl_args.nr_threads);
+
+ *pid = mingw_spawnvpe(args.v[0], args.v, NULL, NULL, in, out, out);
+ close(in);
@@ t/helper/test-simple-ipc.c (new)
+ * let it get started and begin listening for requests on the socket
+ * before reporting our success.
+ */
-+static int wait_for_server_startup(const char * path, pid_t pid_child,
-+ int max_wait_sec)
++static int wait_for_server_startup(pid_t pid_child)
+{
+ int status;
+ pid_t pid_seen;
@@ t/helper/test-simple-ipc.c (new)
+ time_t time_limit, now;
+
+ time(&time_limit);
-+ time_limit += max_wait_sec;
++ time_limit += cl_args.max_wait_sec;
+
+ for (;;) {
+ pid_seen = waitpid(pid_child, &status, WNOHANG);
@@ t/helper/test-simple-ipc.c (new)
+ * after a timeout on the lock), but we don't
+ * care (who responds) if the socket is live.
+ */
-+ s = ipc_get_active_state(path);
++ s = ipc_get_active_state(cl_args.path);
+ if (s == IPC_STATE__LISTENING)
+ return 0;
+
@@ t/helper/test-simple-ipc.c (new)
+ *
+ * Again, we don't care who services the socket.
+ */
-+ s = ipc_get_active_state(path);
++ s = ipc_get_active_state(cl_args.path);
+ if (s == IPC_STATE__LISTENING)
+ return 0;
+
@@ t/helper/test-simple-ipc.c (new)
+ * more control and better error reporting (and makes it easier to write
+ * unit tests).
+ */
-+static int daemon__start_server(const char *path, int argc, const char **argv)
++static int daemon__start_server(void)
+{
+ pid_t pid_child;
+ int ret;
-+ int max_wait_sec = 60;
-+ struct ipc_server_opts opts = {
-+ .nr_threads = 5
-+ };
-+
-+ const char * const daemon_usage[] = {
-+ N_("test-helper simple-ipc start-daemon [<options>"),
-+ NULL
-+ };
-+
-+ struct option daemon_options[] = {
-+ OPT_INTEGER(0, "max-wait", &max_wait_sec,
-+ N_("seconds to wait for daemon to startup")),
-+ OPT_INTEGER(0, "threads", &opts.nr_threads,
-+ N_("number of threads in server thread pool")),
-+ OPT_END()
-+ };
-+
-+ argc = parse_options(argc, argv, NULL, daemon_options, daemon_usage, 0);
-+
-+ if (max_wait_sec < 0)
-+ max_wait_sec = 0;
-+ if (opts.nr_threads < 1)
-+ opts.nr_threads = 1;
+
+ /*
+ * Run the actual daemon in a background process.
+ */
-+ ret = spawn_server(path, &opts, &pid_child);
++ ret = spawn_server(&pid_child);
+ if (pid_child <= 0)
+ return ret;
+
@@ t/helper/test-simple-ipc.c (new)
+ * Let the parent wait for the child process to get started
+ * and begin listening for requests on the socket.
+ */
-+ ret = wait_for_server_startup(path, pid_child, max_wait_sec);
++ ret = wait_for_server_startup(pid_child);
+
+ return ret;
+}
@@ t/helper/test-simple-ipc.c (new)
+ *
+ * Returns 0 if the server is alive.
+ */
-+static int client__probe_server(const char *path)
++static int client__probe_server(void)
+{
+ enum ipc_active_state s;
+
-+ s = ipc_get_active_state(path);
++ s = ipc_get_active_state(cl_args.path);
+ switch (s) {
+ case IPC_STATE__LISTENING:
+ return 0;
+
+ case IPC_STATE__NOT_LISTENING:
-+ return error("no server listening at '%s'", path);
++ return error("no server listening at '%s'", cl_args.path);
+
+ case IPC_STATE__PATH_NOT_FOUND:
-+ return error("path not found '%s'", path);
++ return error("path not found '%s'", cl_args.path);
+
+ case IPC_STATE__INVALID_PATH:
-+ return error("invalid pipe/socket name '%s'", path);
++ return error("invalid pipe/socket name '%s'", cl_args.path);
+
+ case IPC_STATE__OTHER_ERROR:
+ default:
-+ return error("other error for '%s'", path);
++ return error("other error for '%s'", cl_args.path);
+ }
+}
+
+/*
-+ * Send an IPC command to an already-running server daemon and print the
-+ * response.
++ * Send an IPC command token to an already-running server daemon and
++ * print the response.
+ *
-+ * argv[2] contains a simple (1 word) command that `test_app_cb()` (in
-+ * the daemon process) will understand.
++ * This is a simple 1 word command/token that `test_app_cb()` (in the
++ * daemon process) will understand.
+ */
-+static int client__send_ipc(int argc, const char **argv, const char *path)
++static int client__send_ipc(void)
+{
-+ const char *command = argc > 2 ? argv[2] : "(no command)";
++ const char *command = "(no-command)";
+ struct strbuf buf = STRBUF_INIT;
+ struct ipc_client_connect_options options
+ = IPC_CLIENT_CONNECT_OPTIONS_INIT;
+
++ if (cl_args.token && *cl_args.token)
++ command = cl_args.token;
++
+ options.wait_if_busy = 1;
+ options.wait_if_not_found = 0;
+
-+ if (!ipc_client_send_command(path, &options, command, &buf)) {
++ if (!ipc_client_send_command(cl_args.path, &options, command, &buf)) {
+ if (buf.len) {
+ printf("%s\n", buf.buf);
+ fflush(stdout);
@@ t/helper/test-simple-ipc.c (new)
+ return 0;
+ }
+
-+ return error("failed to send '%s' to '%s'", command, path);
++ return error("failed to send '%s' to '%s'", command, cl_args.path);
+}
+
+/*
@@ t/helper/test-simple-ipc.c (new)
+ * event in the server, so we spin and wait here for it to actually
+ * shutdown to make the unit tests a little easier to write.
+ */
-+static int client__stop_server(int argc, const char **argv, const char *path)
++static int client__stop_server(void)
+{
-+ const char *send_quit[] = { argv[0], "send", "quit", NULL };
-+ int max_wait_sec = 60;
+ int ret;
+ time_t time_limit, now;
+ enum ipc_active_state s;
+
-+ const char * const stop_usage[] = {
-+ N_("test-helper simple-ipc stop-daemon [<options>]"),
-+ NULL
-+ };
-+
-+ struct option stop_options[] = {
-+ OPT_INTEGER(0, "max-wait", &max_wait_sec,
-+ N_("seconds to wait for daemon to stop")),
-+ OPT_END()
-+ };
-+
-+ argc = parse_options(argc, argv, NULL, stop_options, stop_usage, 0);
-+
-+ if (max_wait_sec < 0)
-+ max_wait_sec = 0;
-+
+ time(&time_limit);
-+ time_limit += max_wait_sec;
++ time_limit += cl_args.max_wait_sec;
++
++ cl_args.token = "quit";
+
-+ ret = client__send_ipc(3, send_quit, path);
++ ret = client__send_ipc();
+ if (ret)
+ return ret;
+
+ for (;;) {
+ sleep_millisec(100);
+
-+ s = ipc_get_active_state(path);
++ s = ipc_get_active_state(cl_args.path);
+
+ if (s != IPC_STATE__LISTENING) {
+ /*
@@ t/helper/test-simple-ipc.c (new)
+/*
+ * Send an IPC command with ballast to an already-running server daemon.
+ */
-+static int client__sendbytes(int argc, const char **argv, const char *path)
++static int client__sendbytes(void)
+{
-+ int bytecount = 1024;
-+ char *string = "x";
-+ const char * const sendbytes_usage[] = {
-+ N_("test-helper simple-ipc sendbytes [<options>]"),
-+ NULL
-+ };
-+ struct option sendbytes_options[] = {
-+ OPT_INTEGER(0, "bytecount", &bytecount, N_("number of bytes")),
-+ OPT_STRING(0, "byte", &string, N_("byte"), N_("ballast")),
-+ OPT_END()
-+ };
+ struct ipc_client_connect_options options
+ = IPC_CLIENT_CONNECT_OPTIONS_INIT;
+
@@ t/helper/test-simple-ipc.c (new)
+ options.wait_if_not_found = 0;
+ options.uds_disallow_chdir = 0;
+
-+ argc = parse_options(argc, argv, NULL, sendbytes_options, sendbytes_usage, 0);
-+
-+ return do_sendbytes(bytecount, string[0], path, &options);
++ return do_sendbytes(cl_args.bytecount, cl_args.bytevalue, cl_args.path,
++ &options);
+}
+
+struct multiple_thread_data {
@@ t/helper/test-simple-ipc.c (new)
+ * Start a client-side thread pool. Each thread sends a series of
+ * IPC requests. Each request is on a new connection to the server.
+ */
-+static int client__multiple(int argc, const char **argv, const char *path)
++static int client__multiple(void)
+{
+ struct multiple_thread_data *list = NULL;
+ int k;
-+ int nr_threads = 5;
-+ int bytecount = 1;
-+ int batchsize = 10;
+ int sum_join_errors = 0;
+ int sum_thread_errors = 0;
+ int sum_good = 0;
+
-+ const char * const multiple_usage[] = {
-+ N_("test-helper simple-ipc multiple [<options>]"),
-+ NULL
-+ };
-+ struct option multiple_options[] = {
-+ OPT_INTEGER(0, "bytecount", &bytecount, N_("number of bytes")),
-+ OPT_INTEGER(0, "threads", &nr_threads, N_("number of threads")),
-+ OPT_INTEGER(0, "batchsize", &batchsize, N_("number of requests per thread")),
-+ OPT_END()
-+ };
-+
-+ argc = parse_options(argc, argv, NULL, multiple_options, multiple_usage, 0);
-+
-+ if (bytecount < 1)
-+ bytecount = 1;
-+ if (nr_threads < 1)
-+ nr_threads = 1;
-+ if (batchsize < 1)
-+ batchsize = 1;
-+
-+ for (k = 0; k < nr_threads; k++) {
++ for (k = 0; k < cl_args.nr_threads; k++) {
+ struct multiple_thread_data *d = xcalloc(1, sizeof(*d));
+ d->next = list;
-+ d->path = path;
-+ d->bytecount = bytecount + batchsize*(k/26);
-+ d->batchsize = batchsize;
++ d->path = cl_args.path;
++ d->bytecount = cl_args.bytecount + cl_args.batchsize*(k/26);
++ d->batchsize = cl_args.batchsize;
+ d->sum_errors = 0;
+ d->sum_good = 0;
+ d->letter = 'A' + (k % 26);
@@ t/helper/test-simple-ipc.c (new)
+
+int cmd__simple_ipc(int argc, const char **argv)
+{
-+ const char *path = "ipc-test";
++ const char * const simple_ipc_usage[] = {
++ N_("test-helper simple-ipc is-active [<name>] [<options>]"),
++ N_("test-helper simple-ipc run-daemon [<name>] [<threads>]"),
++ N_("test-helper simple-ipc start-daemon [<name>] [<threads>] [<max-wait>]"),
++ N_("test-helper simple-ipc stop-daemon [<name>] [<max-wait>]"),
++ N_("test-helper simple-ipc send [<name>] [<token>]"),
++ N_("test-helper simple-ipc sendbytes [<name>] [<bytecount>] [<byte>]"),
++ N_("test-helper simple-ipc multiple [<name>] [<threads>] [<bytecount>] [<batchsize>]"),
++ NULL
++ };
++
++ const char *bytevalue = NULL;
++
++ struct option options[] = {
++#ifndef GIT_WINDOWS_NATIVE
++ OPT_STRING(0, "name", &cl_args.path, N_("name"), N_("name or pathname of unix domain socket")),
++#else
++ OPT_STRING(0, "name", &cl_args.path, N_("name"), N_("named-pipe name")),
++#endif
++ OPT_INTEGER(0, "threads", &cl_args.nr_threads, N_("number of threads in server thread pool")),
++ OPT_INTEGER(0, "max-wait", &cl_args.max_wait_sec, N_("seconds to wait for daemon to start or stop")),
++
++ OPT_INTEGER(0, "bytecount", &cl_args.bytecount, N_("number of bytes")),
++ OPT_INTEGER(0, "batchsize", &cl_args.batchsize, N_("number of requests per thread")),
++
++ OPT_STRING(0, "byte", &bytevalue, N_("byte"), N_("ballast character")),
++ OPT_STRING(0, "token", &cl_args.token, N_("token"), N_("command token to send to the server")),
++
++ OPT_END()
++ };
++
++ if (argc < 2)
++ usage_with_options(simple_ipc_usage, options);
++
++ if (argc == 2 && !strcmp(argv[1], "-h"))
++ usage_with_options(simple_ipc_usage, options);
+
+ if (argc == 2 && !strcmp(argv[1], "SUPPORTS_SIMPLE_IPC"))
+ return 0;
+
++ cl_args.subcommand = argv[1];
++
++ argc--;
++ argv++;
++
++ argc = parse_options(argc, argv, NULL, options, simple_ipc_usage, 0);
++
++ if (cl_args.nr_threads < 1)
++ cl_args.nr_threads = 1;
++ if (cl_args.max_wait_sec < 0)
++ cl_args.max_wait_sec = 0;
++ if (cl_args.bytecount < 1)
++ cl_args.bytecount = 1;
++ if (cl_args.batchsize < 1)
++ cl_args.batchsize = 1;
++
++ if (bytevalue && *bytevalue)
++ cl_args.bytevalue = bytevalue[0];
++
+ /*
+ * Use '!!' on all dispatch functions to map from `error()` style
+ * (returns -1) style to `test_must_fail` style (expects 1). This
+ * makes shell error messages less confusing.
+ */
+
-+ if (argc == 2 && !strcmp(argv[1], "is-active"))
-+ return !!client__probe_server(path);
++ if (!strcmp(cl_args.subcommand, "is-active"))
++ return !!client__probe_server();
+
-+ if (argc >= 2 && !strcmp(argv[1], "run-daemon"))
-+ return !!daemon__run_server(path, argc, argv);
++ if (!strcmp(cl_args.subcommand, "run-daemon"))
++ return !!daemon__run_server();
+
-+ if (argc >= 2 && !strcmp(argv[1], "start-daemon"))
-+ return !!daemon__start_server(path, argc, argv);
++ if (!strcmp(cl_args.subcommand, "start-daemon"))
++ return !!daemon__start_server();
+
+ /*
+ * Client commands follow. Ensure a server is running before
-+ * going any further.
++ * sending any data. This might be overkill, but then again
++ * this is a test harness.
+ */
-+ if (client__probe_server(path))
-+ return 1;
+
-+ if (argc >= 2 && !strcmp(argv[1], "stop-daemon"))
-+ return !!client__stop_server(argc, argv, path);
++ if (!strcmp(cl_args.subcommand, "stop-daemon")) {
++ if (client__probe_server())
++ return 1;
++ return !!client__stop_server();
++ }
+
-+ if ((argc == 2 || argc == 3) && !strcmp(argv[1], "send"))
-+ return !!client__send_ipc(argc, argv, path);
++ if (!strcmp(cl_args.subcommand, "send")) {
++ if (client__probe_server())
++ return 1;
++ return !!client__send_ipc();
++ }
+
-+ if (argc >= 2 && !strcmp(argv[1], "sendbytes"))
-+ return !!client__sendbytes(argc, argv, path);
++ if (!strcmp(cl_args.subcommand, "sendbytes")) {
++ if (client__probe_server())
++ return 1;
++ return !!client__sendbytes();
++ }
+
-+ if (argc >= 2 && !strcmp(argv[1], "multiple"))
-+ return !!client__multiple(argc, argv, path);
++ if (!strcmp(cl_args.subcommand, "multiple")) {
++ if (client__probe_server())
++ return 1;
++ return !!client__multiple();
++ }
+
-+ die("Unhandled argv[1]: '%s'", argv[1]);
++ die("Unhandled subcommand: '%s'", cl_args.subcommand);
+}
+#endif
@@ t/t0052-simple-ipc.sh (new)
+'
+
+test_expect_success 'simple command server' '
-+ test-tool simple-ipc send ping >actual &&
++ test-tool simple-ipc send --token=ping >actual &&
+ echo pong >expect &&
+ test_cmp expect actual
+'
@@ t/t0052-simple-ipc.sh (new)
+'
+
+test_expect_success 'big response' '
-+ test-tool simple-ipc send big >actual &&
++ test-tool simple-ipc send --token=big >actual &&
+ test_line_count -ge 10000 actual &&
+ grep -q "big: [0]*9999\$" actual
+'
+
+test_expect_success 'chunk response' '
-+ test-tool simple-ipc send chunk >actual &&
++ test-tool simple-ipc send --token=chunk >actual &&
+ test_line_count -ge 10000 actual &&
+ grep -q "big: [0]*9999\$" actual
+'
+
+test_expect_success 'slow response' '
-+ test-tool simple-ipc send slow >actual &&
++ test-tool simple-ipc send --token=slow >actual &&
+ test_line_count -ge 100 actual &&
+ grep -q "big: [0]*99\$" actual
+'
@@ t/t0052-simple-ipc.sh (new)
+test_expect_success 'stop-daemon works' '
+ test-tool simple-ipc stop-daemon &&
+ test_must_fail test-tool simple-ipc is-active &&
-+ test_must_fail test-tool simple-ipc send ping
++ test_must_fail test-tool simple-ipc send --token=ping
+'
+
+test_doneSignature · 1 line
gitgitgadget
PATCH v5 1/12 pkt-line: eliminate the need for static buffer in packet_write_gently()
From: Jeff Hostetler <[email protected]>
Teach packet_write_gently() to write the pkt-line header and the actual buffer in 2 separate calls to write_in_full() and avoid the need for a static buffer, thread-safe scratch space, or an excessively large stack buffer.
Change write_packetized_from_fd() to allocate a temporary buffer rather than using a static buffer to avoid similar issues here.
These changes are intended to make it easier to use pkt-line routines in a multi-threaded context with multiple concurrent writers writing to different streams.
--- pkt-line.c | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-)
pkt-line.c +19 -8
@@ -196,17 +196,25 @@ int packet_write_fmt_gently(int fd, const char *fmt, ...)static int packet_write_gently (const int fd_out,const char *buf,size_t size) { -static char packet_write_buffer[LARGE_PACKET_MAX ]; +char header[4 ];size_t packet_size; -if (size >sizeof (packet_write_buffer) -4 ) +if (size >LARGE_PACKET_DATA_MAX )return error (_ ("packet write failed - data exceeds max packet size" ));packet_trace (buf, size,1 ); packet_size = size +4 ; -set_packet_header (packet_write_buffer, packet_size); -memcpy (packet_write_buffer +4 , buf, size); -if (write_in_full (fd_out, packet_write_buffer, packet_size) <0 ) + +set_packet_header (header, packet_size); + +/* +* Write the header and the buffer in 2 parts so that we do not need +* to allocate a buffer or rely on a static buffer. This avoids perf +* and multi-threading issues. +*/ + +if (write_in_full (fd_out, header,4 ) <0 || +write_in_full (fd_out, buf, size) <0 )return error (_ ("packet write failed" ));return 0 ; }
@@ -244,20 +252,23 @@ void packet_buf_write_len(struct strbuf *buf, const char *data, size_t len)int write_packetized_from_fd (int fd_in,int fd_out) { -static char buf[LARGE_PACKET_DATA_MAX ]; +char *buf =xmalloc (LARGE_PACKET_DATA_MAX );int err =0 ;ssize_t bytes_to_write;while (!err) { - bytes_to_write =xread (fd_in, buf,sizeof (buf)); -if (bytes_to_write <0 ) + bytes_to_write =xread (fd_in, buf,LARGE_PACKET_DATA_MAX ); +if (bytes_to_write <0 ) { +free (buf);return COPY_READ_ERROR ; + }if (bytes_to_write ==0 )break ; err =packet_write_gently (fd_out, buf, bytes_to_write); }if (!err) err =packet_flush_gently (fd_out); +free (buf);return err; }
Signature · 1 line
gitgitgadget
PATCH v5 5/12 simple-ipc: design documentation for new IPC mechanism
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Brief design documentation for new IPC mechanism allowing foreground Git client to talk with an existing daemon process at a known location using a named pipe or unix domain socket.
--- Documentation/technical/api-simple-ipc.txt | 105 +++++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 Documentation/technical/api-simple-ipc.txt
Documentation/technical/api-simple-ipc.txt new file +105 -0
@@ -0,0 +1,105 @@ +Simple-IPC API +============== + +The Simple-IPC API is a collection of `ipc_` prefixed library routines +and a basic communication protocol that allow an IPC-client process to +send an application-specific IPC-request message to an IPC-server +process and receive an application-specific IPC-response message. + +Communication occurs over a named pipe on Windows and a Unix domain +socket on other platforms. IPC-clients and IPC-servers rendezvous at +a previously agreed-to application-specific pathname (which is outside +the scope of this design) that is local to the computer system. + +The IPC-server routines within the server application process create a +thread pool to listen for connections and receive request messages +from multiple concurrent IPC-clients. When received, these messages +are dispatched up to the server application callbacks for handling. +IPC-server routines then incrementally relay responses back to the +IPC-client. + +The IPC-client routines within a client application process connect +to the IPC-server and send a request message and wait for a response. +When received, the response is returned back the caller. + +For example, the `fsmonitor--daemon` feature will be built as a server +application on top of the IPC-server library routines. It will have +threads watching for file system events and a thread pool waiting for +client connections. Clients, such as `git status` will request a list +of file system events since a point in time and the server will +respond with a list of changed files and directories. The formats of +the request and response are application-specific; the IPC-client and +IPC-server routines treat them as opaque byte streams. + + +Comparison with sub-process model +--------------------------------- + +The Simple-IPC mechanism differs from the existing `sub-process.c` +model (Documentation/technical/long-running-process-protocol.txt) and +used by applications like Git-LFS. In the LFS-style sub-process model +the helper is started by the foreground process, communication happens +via a pair of file descriptors bound to the stdin/stdout of the +sub-process, the sub-process only serves the current foreground +process, and the sub-process exits when the foreground process +terminates. + +In the Simple-IPC model the server is a very long-running service. It +can service many clients at the same time and has a private socket or +named pipe connection to each active client. It might be started +(on-demand) by the current client process or it might have been +started by a previous client or by the OS at boot time. The server +process is not associated with a terminal and it persists after +clients terminate. Clients do not have access to the stdin/stdout of +the server process and therefore must communicate over sockets or +named pipes. + + +Server startup and shutdown +--------------------------- + +How an application server based upon IPC-server is started is also +outside the scope of the Simple-IPC design and is a property of the +application using it. For example, the server might be started or +restarted during routine maintenance operations, or it might be +started as a system service during the system boot-up sequence, or it +might be started on-demand by a foreground Git command when needed. + +Similarly, server shutdown is a property of the application using +the simple-ipc routines. For example, the server might decide to +shutdown when idle or only upon explicit request. + + +Simple-IPC protocol +------------------- + +The Simple-IPC protocol consists of a single request message from the +client and an optional response message from the server. Both the +client and server messages are unlimited in length and are terminated +with a flush packet. + +The pkt-line routines (Documentation/technical/protocol-common.txt) +are used to simplify buffer management during message generation, +transmission, and reception. A flush packet is used to mark the end +of the message. This allows the sender to incrementally generate and +transmit the message. It allows the receiver to incrementally receive +the message in chunks and to know when they have received the entire +message. + +The actual byte format of the client request and server response +messages are application specific. The IPC layer transmits and +receives them as opaque byte buffers without any concern for the +content within. It is the job of the calling application layer to +understand the contents of the request and response messages. + + +Summary +------- + +Conceptually, the Simple-IPC protocol is similar to an HTTP REST +request. Clients connect, make an application-specific and +stateless request, receive an application-specific +response, and disconnect. It is a one round trip facility for +querying the server. The Simple-IPC routines hide the socket, +named pipe, and thread pool details and allow the application +layer to focus on the application at hand.
Signature · 1 line
gitgitgadget
PATCH v5 6/12 simple-ipc: add win32 implementation
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Create Windows implementation of "simple-ipc" using named pipes.
--- Makefile | 5 + compat/simple-ipc/ipc-shared.c | 28 ++ compat/simple-ipc/ipc-win32.c | 751 ++++++++++++++++++++++++++++ config.mak.uname | 2 + contrib/buildsystems/CMakeLists.txt | 4 + simple-ipc.h | 228 +++++++++ 6 files changed, 1018 insertions(+) create mode 100644 compat/simple-ipc/ipc-shared.c create mode 100644 compat/simple-ipc/ipc-win32.c create mode 100644 simple-ipc.h
Makefile +5 -0
@@ -1667,6 +1667,11 @@ else LIB_OBJS += unix-socket.o endif +ifdef USE_WIN32_IPC + LIB_OBJS += compat/simple-ipc/ipc-shared.o + LIB_OBJS += compat/simple-ipc/ipc-win32.o +endif + ifdef NO_ICONV BASIC_CFLAGS += -DNO_ICONV endif
compat/simple-ipc/ipc-shared.c new file +28 -0
@@ -0,0 +1,28 @@ +#include "cache.h" +#include "simple-ipc.h" +#include "strbuf.h" +#include "pkt-line.h" +#include "thread-utils.h" + +#ifdef SUPPORTS_SIMPLE_IPC + +int ipc_server_run (const char *path,const struct ipc_server_opts *opts, +ipc_server_application_cb *application_cb, +void *application_data) +{ +struct ipc_server_data *server_data =NULL ; +int ret; + + ret =ipc_server_run_async (&server_data, path, opts, + application_cb, application_data); +if (ret) +return ret; + + ret =ipc_server_await (server_data); + +ipc_server_free (server_data); + +return ret; +} + +#endif /* SUPPORTS_SIMPLE_IPC */
compat/simple-ipc/ipc-win32.c new file +751 -0
751 lines not on this page · raw message
config.mak.uname +2 -0
@@ -421,6 +421,7 @@ ifeq ($(uname_S),Windows) RUNTIME_PREFIX = YesPlease HAVE_WPGMPTR = YesWeDo NO_ST_BLOCKS_IN_STRUCT_STAT = YesPlease + USE_WIN32_IPC = YesPlease USE_WIN32_MMAP = YesPlease MMAP_PREVENTS_DELETE = UnfortunatelyYes # USE_NED_ALLOCATOR = YesPlease
@@ -597,6 +598,7 @@ ifneq (,$(findstring MINGW,$(uname_S))) RUNTIME_PREFIX = YesPlease HAVE_WPGMPTR = YesWeDo NO_ST_BLOCKS_IN_STRUCT_STAT = YesPlease + USE_WIN32_IPC = YesPlease USE_WIN32_MMAP = YesPlease MMAP_PREVENTS_DELETE = UnfortunatelyYes USE_NED_ALLOCATOR = YesPlease
contrib/buildsystems/CMakeLists.txt +4 -0
@@ -246,6 +246,10 @@ elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux") list(APPEND compat_SOURCES unix-socket.c) endif() +if(CMAKE_SYSTEM_NAME STREQUAL "Windows") + list(APPEND compat_SOURCES compat/simple-ipc/ipc-shared.c compat/simple-ipc/ipc-win32.c) +endif() + set(EXE_EXTENSION ${CMAKE_EXECUTABLE_SUFFIX}) #header checks
simple-ipc.h new file +228 -0
228 lines not on this page · raw message
Signature · 1 line
gitgitgadget
PATCH v5 7/12 unix-socket: eliminate static unix_stream_socket() helper function
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
The static helper function unix_stream_socket() calls die(). This is not appropriate for all callers. Eliminate the wrapper function and make the callers propagate the error.
--- unix-socket.c | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-)
unix-socket.c +13 -14
@@ -1,14 +1,6 @@#include "cache.h" #include "unix-socket.h" -static int unix_stream_socket (void ) -{ -int fd =socket (AF_UNIX ,SOCK_STREAM ,0 ); -if (fd <0 ) -die_errno ("unable to create socket" ); -return fd; -} -static int chdir_len (const char *orig,int len) {char *path =xmemdupz (orig, len);
@@ -73,13 +65,16 @@ static int unix_sockaddr_init(struct sockaddr_un *sa, const char *path,int unix_stream_connect (const char *path) { -int fd, saved_errno; +int fd =-1 , saved_errno;struct sockaddr_un sa;struct unix_sockaddr_context ctx;if (unix_sockaddr_init (&sa, path, &ctx) <0 )return -1 ; - fd =unix_stream_socket (); + fd =socket (AF_UNIX ,SOCK_STREAM ,0 ); +if (fd <0 ) + goto fail; +if (connect (fd, (struct sockaddr *)&sa,sizeof (sa)) <0 ) goto fail;unix_sockaddr_cleanup (&ctx);
@@ -87,15 +82,16 @@ int unix_stream_connect(const char *path) fail: saved_errno = errno; +if (fd !=-1 ) +close (fd);unix_sockaddr_cleanup (&ctx); -close (fd); errno = saved_errno;return -1 ; }int unix_stream_listen (const char *path) { -int fd, saved_errno; +int fd =-1 , saved_errno;struct sockaddr_un sa;struct unix_sockaddr_context ctx;
@@ -103,7 +99,9 @@ int unix_stream_listen(const char *path)if (unix_sockaddr_init (&sa, path, &ctx) <0 )return -1 ; - fd =unix_stream_socket (); + fd =socket (AF_UNIX ,SOCK_STREAM ,0 ); +if (fd <0 ) + goto fail;if (bind (fd, (struct sockaddr *)&sa,sizeof (sa)) <0 ) goto fail;
@@ -116,8 +114,9 @@ int unix_stream_listen(const char *path) fail: saved_errno = errno; +if (fd !=-1 ) +close (fd);unix_sockaddr_cleanup (&ctx); -close (fd); errno = saved_errno;return -1 ; }
Signature · 1 line
gitgitgadget
PATCH v5 11/12 simple-ipc: add Unix domain socket implementation
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Create Unix domain socket based implementation of "simple-ipc".
A set of ipc_client routines implement a client library to connect to an ipc_server over a Unix domain socket, send a simple request, and receive a single response. Clients use blocking IO on the socket.
A set of ipc_server routines implement a thread pool to listen for and concurrently service client connections.
The server creates a new Unix domain socket at a known location. If a socket already exists with that name, the server tries to determine if another server is already listening on the socket or if the socket is dead. If socket is busy, the server exits with an error rather than stealing the socket. If the socket is dead, the server creates a new one and starts up.
If while running, the server detects that its socket has been stolen by another server, it automatically exits.
--- Makefile | 2 + compat/simple-ipc/ipc-unix-socket.c | 986 ++++++++++++++++++++++++++++ contrib/buildsystems/CMakeLists.txt | 2 + simple-ipc.h | 13 +- 4 files changed, 1002 insertions(+), 1 deletion(-) create mode 100644 compat/simple-ipc/ipc-unix-socket.c
Makefile +2 -0
@@ -1666,6 +1666,8 @@ ifdef NO_UNIX_SOCKETS else LIB_OBJS += unix-socket.o LIB_OBJS += unix-stream-server.o + LIB_OBJS += compat/simple-ipc/ipc-shared.o + LIB_OBJS += compat/simple-ipc/ipc-unix-socket.o endif ifdef USE_WIN32_IPC
compat/simple-ipc/ipc-unix-socket.c new file +986 -0
986 lines not on this page · raw message
contrib/buildsystems/CMakeLists.txt +2 -0
@@ -248,6 +248,8 @@ endif() if(CMAKE_SYSTEM_NAME STREQUAL "Windows") list(APPEND compat_SOURCES compat/simple-ipc/ipc-shared.c compat/simple-ipc/ipc-win32.c) +else() + list(APPEND compat_SOURCES compat/simple-ipc/ipc-shared.c compat/simple-ipc/ipc-unix-socket.c) endif() set(EXE_EXTENSION ${CMAKE_EXECUTABLE_SUFFIX})
simple-ipc.h +12 -1
@@ -5,7 +5,7 @@* See Documentation/technical/api-simple-ipc.txt */ -#if defined(GIT_WINDOWS_NATIVE ) +#if defined(GIT_WINDOWS_NATIVE ) || !defined(NO_UNIX_SOCKETS )#define SUPPORTS_SIMPLE_IPC #endif
@@ -62,11 +62,17 @@ struct ipc_client_connect_options {* the service and need to wait for it to become ready. */ unsigned int wait_if_not_found:1; + +/* +* Disallow chdir() when creating a Unix domain socket. +*/ + unsigned int uds_disallow_chdir:1; };#define IPC_CLIENT_CONNECT_OPTIONS_INIT { \ .wait_if_busy = 0, \ .wait_if_not_found = 0, \ + .uds_disallow_chdir = 0, \ }/*
@@ -159,6 +165,11 @@ struct ipc_server_data;struct ipc_server_opts {int nr_threads; + +/* +* Disallow chdir() when creating a Unix domain socket. +*/ +unsigned int uds_disallow_chdir:1 ; };/*
Signature · 1 line
gitgitgadget
PATCH v5 2/12 pkt-line: do not issue flush packets in write_packetized_*()
In reply to Jeff Hostetler via GitGitGadgetFrom: Johannes Schindelin <[email protected]>
Remove the packet_flush_gently() call in write_packetized_from_buf() and write_packetized_from_fd() and require the caller to call it if desired. Rename both functions to write_packetized_from_*_no_flush()` to prevent later merge accidents.
write_packetized_from_buf() currently only has one caller:apply_multi_file_filter() in convert.c. It always wants a flush packet to be written after writing the payload.
However, we are about to introduce a caller that wants to write many packets before a final flush packet, so let's make the caller responsible for emitting the flush packet.
--- convert.c | 8 ++++++-- pkt-line.c | 8 ++------ pkt-line.h | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-)
convert.c +6 -2
@@ -884,9 +884,13 @@ static int apply_multi_file_filter(const char *path, const char *src, size_t len goto done;if (fd >=0 ) - err =write_packetized_from_fd (fd, process->in); + err =write_packetized_from_fd_no_flush (fd, process->in);else - err =write_packetized_from_buf (src, len, process->in); + err =write_packetized_from_buf_no_flush (src, len, process->in); +if (err) + goto done; + + err =packet_flush_gently (process->in);if (err) goto done;
pkt-line.c +2 -6
@@ -250,7 +250,7 @@ void packet_buf_write_len(struct strbuf *buf, const char *data, size_t len)packet_trace (data, len,1 ); } -int write_packetized_from_fd (int fd_in,int fd_out) +int write_packetized_from_fd_no_flush (int fd_in,int fd_out) {char *buf =xmalloc (LARGE_PACKET_DATA_MAX );int err =0 ;
@@ -266,13 +266,11 @@ int write_packetized_from_fd(int fd_in, int fd_out)break ; err =packet_write_gently (fd_out, buf, bytes_to_write); } -if (!err) - err =packet_flush_gently (fd_out);free (buf);return err; } -int write_packetized_from_buf (const char *src_in,size_t len,int fd_out) +int write_packetized_from_buf_no_flush (const char *src_in,size_t len,int fd_out) {int err =0 ;size_t bytes_written =0 ;
@@ -288,8 +286,6 @@ int write_packetized_from_buf(const char *src_in, size_t len, int fd_out) err =packet_write_gently (fd_out, src_in + bytes_written, bytes_to_write); bytes_written += bytes_to_write; } -if (!err) - err =packet_flush_gently (fd_out);return err; }
pkt-line.h +2 -2
@@ -32,8 +32,8 @@ void packet_buf_write(struct strbuf *buf, const char *fmt, ...) __attribute__((fvoid packet_buf_write_len (struct strbuf *buf,const char *data,size_t len);int packet_flush_gently (int fd);int packet_write_fmt_gently (int fd,const char *fmt, ...) __attribute__((format (printf,2 ,3 ))); -int write_packetized_from_fd (int fd_in,int fd_out); -int write_packetized_from_buf (const char *src_in,size_t len,int fd_out); +int write_packetized_from_fd_no_flush (int fd_in,int fd_out); +int write_packetized_from_buf_no_flush (const char *src_in,size_t len,int fd_out);/* * Read a packetized line into the buffer, which must be at least size bytes
Signature · 1 line
gitgitgadget
PATCH v5 3/12 pkt-line: add PACKET_READ_GENTLE_ON_READ_ERROR option
In reply to Jeff Hostetler via GitGitGadgetFrom: Johannes Schindelin <[email protected]>
Introduce PACKET_READ_GENTLE_ON_READ_ERROR option to help libify the packet readers.
So far, the (possibly indirect) callers of get_packet_data() can ask that function to return an error instead of die()ing upon end-of-file. However, random read errors will still cause the process to die.
So let's introduce an explicit option to tell the packet reader machinery to please be nice and only return an error on read errors.
This change prepares pkt-line for use by long-running daemon processes. Such processes should be able to serve multiple concurrent clients and and survive random IO errors. If there is an error on one connection, a daemon should be able to drop that connection and continue serving existing and future connections.
This ability will be used by a Git-aware "Builtin FSMonitor" feature in a later patch series.
--- pkt-line.c | 19 +++++++++++++++++-- pkt-line.h | 11 ++++++++--- 2 files changed, 25 insertions(+), 5 deletions(-)
pkt-line.c +17 -2
@@ -305,8 +305,11 @@ static int get_packet_data(int fd, char **src_buf, size_t *src_size, *src_size -= ret; }else { ret =read_in_full (fd, dst, size); -if (ret <0 ) +if (ret <0 ) { +if (options &PACKET_READ_GENTLE_ON_READ_ERROR ) +return error_errno (_ ("read error" ));die_errno (_ ("read error" )); + } }/* And complain if we didn't get enough bytes to satisfy the read. */
@@ -314,6 +317,8 @@ static int get_packet_data(int fd, char **src_buf, size_t *src_size,if (options &PACKET_READ_GENTLE_ON_EOF )return -1 ; +if (options &PACKET_READ_GENTLE_ON_READ_ERROR ) +return error (_ ("the remote end hung up unexpectedly" ));die (_ ("the remote end hung up unexpectedly" )); }
@@ -342,6 +347,9 @@ enum packet_read_status packet_read_with_status(int fd, char **src_buffer, len =packet_length (linelen);if (len <0 ) { +if (options &PACKET_READ_GENTLE_ON_READ_ERROR ) +return error (_ ("protocol error: bad line length " +"character: %.4s" ), linelen);die (_ ("protocol error: bad line length character: %.4s" ), linelen); }else if (!len) {packet_trace ("0000" ,4 ,0 );
@@ -356,12 +364,19 @@ enum packet_read_status packet_read_with_status(int fd, char **src_buffer, *pktlen =0 ;return PACKET_READ_RESPONSE_END ; }else if (len <4 ) { +if (options &PACKET_READ_GENTLE_ON_READ_ERROR ) +return error (_ ("protocol error: bad line length %d" ), + len);die (_ ("protocol error: bad line length %d" ), len); } len -=4 ; -if ((unsigned )len >= size) +if ((unsigned )len >= size) { +if (options &PACKET_READ_GENTLE_ON_READ_ERROR ) +return error (_ ("protocol error: bad line length %d" ), + len);die (_ ("protocol error: bad line length %d" ), len); + }if (get_packet_data (fd, src_buffer, src_len, buffer, len, options) <0 ) { *pktlen =-1 ;
pkt-line.h +8 -3
@@ -68,10 +68,15 @@ int write_packetized_from_buf_no_flush(const char *src_in, size_t len, int fd_ou* * If options contains PACKET_READ_DIE_ON_ERR_PACKET, it dies when it sees an * ERR packet. +* +* If options contains PACKET_READ_GENTLE_ON_READ_ERROR, we will not die +* on read errors, but instead return -1. However, we may still die on an +* ERR packet (if requested). */ -#define PACKET_READ_GENTLE_ON_EOF (1u<<0) -#define PACKET_READ_CHOMP_NEWLINE (1u<<1) -#define PACKET_READ_DIE_ON_ERR_PACKET (1u<<2) +#define PACKET_READ_GENTLE_ON_EOF (1u<<0) +#define PACKET_READ_CHOMP_NEWLINE (1u<<1) +#define PACKET_READ_DIE_ON_ERR_PACKET (1u<<2) +#define PACKET_READ_GENTLE_ON_READ_ERROR (1u<<3)int packet_read (int fd,char **src_buffer,size_t *src_len,char *buffer,unsigned size,int options);
Signature · 1 line
gitgitgadget
PATCH v5 4/12 pkt-line: add options argument to read_packetized_to_strbuf()
In reply to Jeff Hostetler via GitGitGadgetFrom: Johannes Schindelin <[email protected]>
Update the calling sequence of read_packetized_to_strbuf() to take an options argument and not assume a fixed set of options. Update the only existing caller accordingly to explicitly pass the formerly-assumed flags.
The read_packetized_to_strbuf() function calls packet_read() with a fixed set of assumed options (PACKET_READ_GENTLE_ON_EOF). This assumption has been fine for the single existing caller apply_multi_file_filter() in convert.c.
In a later commit we would like to add other callers to read_packetized_to_strbuf() that need a different set of options.
--- convert.c | 3 ++- pkt-line.c | 4 ++-- pkt-line.h | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-)
convert.c +2 -1
@@ -907,7 +907,8 @@ static int apply_multi_file_filter(const char *path, const char *src, size_t lenif (err) goto done; - err =read_packetized_to_strbuf (process->out, &nbuf) <0 ; + err =read_packetized_to_strbuf (process->out, &nbuf, +PACKET_READ_GENTLE_ON_EOF ) <0 ;if (err) goto done;
pkt-line.c +2 -2
@@ -443,7 +443,7 @@ char *packet_read_line_buf(char **src, size_t *src_len, int *dst_len)return packet_read_line_generic (-1 , src, src_len, dst_len); } -ssize_t read_packetized_to_strbuf (int fd_in,struct strbuf *sb_out) +ssize_t read_packetized_to_strbuf (int fd_in,struct strbuf *sb_out,int options) {int packet_len;
@@ -459,7 +459,7 @@ ssize_t read_packetized_to_strbuf(int fd_in, struct strbuf *sb_out)* that there is already room for the extra byte. */ sb_out->buf + sb_out->len,LARGE_PACKET_DATA_MAX +1 , - PACKET_READ_GENTLE_ON_EOF); + options);if (packet_len <=0 )break ; sb_out->len += packet_len;
pkt-line.h +1 -1
@@ -136,7 +136,7 @@ char *packet_read_line_buf(char **src_buf, size_t *src_len, int *size);/* * Reads a stream of variable sized packets until a flush packet is detected. */ -ssize_t read_packetized_to_strbuf (int fd_in,struct strbuf *sb_out); +ssize_t read_packetized_to_strbuf (int fd_in,struct strbuf *sb_out,int options);/* * Receive multiplexed output stream over git native protocol.
Signature · 1 line
gitgitgadget
PATCH v5 8/12 unix-socket: add backlog size option to unix_stream_listen()
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Update unix_stream_listen() to take an options structure to override default behaviors. This commit includes the size of the listen() backlog.
--- builtin/credential-cache--daemon.c | 3 ++- unix-socket.c | 11 +++++++++-- unix-socket.h | 9 ++++++++- 3 files changed, 19 insertions(+), 4 deletions(-)
builtin/credential-cache--daemon.c +2 -1
@@ -203,9 +203,10 @@ static int serve_cache_loop(int fd)static void serve_cache (const char *socket_path,int debug) { +struct unix_stream_listen_opts opts =UNIX_STREAM_LISTEN_OPTS_INIT ;int fd; - fd =unix_stream_listen (socket_path); + fd =unix_stream_listen (socket_path, &opts);if (fd <0 )die_errno ("unable to bind to '%s'" , socket_path);
unix-socket.c +9 -2
@@ -1,6 +1,8 @@#include "cache.h" #include "unix-socket.h" +#define DEFAULT_UNIX_STREAM_LISTEN_BACKLOG (5) +static int chdir_len (const char *orig,int len) {char *path =xmemdupz (orig, len);
@@ -89,9 +91,11 @@ int unix_stream_connect(const char *path)return -1 ; } -int unix_stream_listen (const char *path) +int unix_stream_listen (const char *path, +const struct unix_stream_listen_opts *opts) {int fd =-1 , saved_errno; +int backlog;struct sockaddr_un sa;struct unix_sockaddr_context ctx;
@@ -106,7 +110,10 @@ int unix_stream_listen(const char *path)if (bind (fd, (struct sockaddr *)&sa,sizeof (sa)) <0 ) goto fail; -if (listen (fd,5 ) <0 ) + backlog = opts->listen_backlog_size; +if (backlog <=0 ) + backlog =DEFAULT_UNIX_STREAM_LISTEN_BACKLOG ; +if (listen (fd, backlog) <0 ) goto fail;unix_sockaddr_cleanup (&ctx);
unix-socket.h +8 -1
@@ -1,7 +1,14 @@#ifndef UNIX_SOCKET_H #define UNIX_SOCKET_H +struct unix_stream_listen_opts { +int listen_backlog_size; +}; + +#define UNIX_STREAM_LISTEN_OPTS_INIT { 0 } +int unix_stream_connect (const char *path); -int unix_stream_listen (const char *path); +int unix_stream_listen (const char *path, +const struct unix_stream_listen_opts *opts);#endif /* UNIX_SOCKET_H */
Signature · 1 line
gitgitgadget