cygrunsrv + sshd + rsync = 20 times too slow -- throttled?

Corinna Vinschen corinna-cygwin@cygwin.com
Thu Sep 2 18:54:11 GMT 2021


Hi Takashi,

On Sep  2 17:15, Takashi Yano wrote:
> On Wed, 1 Sep 2021 10:46:24 +0200
> Corinna Vinschen wrote:
> > On Sep  1 17:23, Takashi Yano wrote:
> > > One idea is:
> > > 
> > > Count read handle and write handle opned using NtQueryObject().
> > > If the numbers of opened handle are equal each other, only
> > > the write side (pair of write handle and query_hdl) is alive.
> > > In this case, write() returns error.
> > > If read side is alive, number of read handles is greater than
> > > number of write handles. 
> > 
> > Interesting idea.  But where do you do the count?  The event object
> > will not get signalled, so WFMO will not return when performing a
> > blocking write.
> 
> I imagined something like attached patch.
> 
> Unfortunately, the attached patch seems to have bug that
> occasionally causes the following error while building
> cygwin1.dll.
> 
> <command-line>: error: "GCC_VERSION" redefined [-Werror]
> <command-line>: note: this is the location of the previous definition
> cc1plus: all warnings being treated as errors
> make[1]: *** [Makefile:1942: fhandler_proc.o] Error 1

> diff --git a/winsup/cygwin/fhandler.h b/winsup/cygwin/fhandler.h
> index 1f0f28077..38390848f 100644
> --- a/winsup/cygwin/fhandler.h
> +++ b/winsup/cygwin/fhandler.h
> @@ -1172,6 +1172,7 @@ class fhandler_pipe: public fhandler_base
>  {
>  private:
>    HANDLE query_hdl;
> +  HANDLE reader_evt;
>    pid_t popen_pid;
>    size_t max_atomic_write;
>    void set_pipe_non_blocking (bool nonblocking);
> @@ -1181,6 +1182,7 @@ public:
>    bool ispipe() const { return true; }
>  
>    HANDLE get_query_handle () const { return query_hdl; }
> +  bool reader_closed ();
>  
>    void set_popen_pid (pid_t pid) {popen_pid = pid;}
>    pid_t get_popen_pid () const {return popen_pid;}
> diff --git a/winsup/cygwin/fhandler_pipe.cc b/winsup/cygwin/fhandler_pipe.cc
> index 2d9e87bb3..4773d04da 100644
> --- a/winsup/cygwin/fhandler_pipe.cc
> +++ b/winsup/cygwin/fhandler_pipe.cc
> @@ -51,6 +51,8 @@ fhandler_pipe::set_pipe_non_blocking (bool nonblocking)
>    fpi.ReadMode = FILE_PIPE_BYTE_STREAM_MODE;
>    fpi.CompletionMode = nonblocking ? FILE_PIPE_COMPLETE_OPERATION
>      : FILE_PIPE_QUEUE_OPERATION;
> +  if (get_device () == FH_PIPEW)
> +    fpi.CompletionMode = FILE_PIPE_COMPLETE_OPERATION;

Ok, so  the write side is always non-blocking...


>    status = NtSetInformationFile (get_handle (), &io, &fpi, sizeof fpi,
>  				 FilePipeInformation);
>    if (!NT_SUCCESS (status))
> @@ -308,6 +310,22 @@ fhandler_pipe::raw_read (void *ptr, size_t& len)
>      }
>    else if (status == STATUS_THREAD_CANCELED)
>      pthread::static_cancel_self ();
> +
> +  if ((ssize_t)len > 0)
> +    SetEvent (reader_evt);
> +}

...and every successful read sets the event object to signalled.

Sounds good.

> +    }
> +
>    if (len <= max_atomic_write)
>      chunk = len;
>    else if (is_nonblocking ())
> @@ -400,10 +425,24 @@ fhandler_pipe::raw_write (const void *ptr, size_t len)
>  	  /* NtWriteFile returns success with # of bytes written == 0
>  	     if writing on a non-blocking pipe fails because the pipe
>  	     buffer doesn't have sufficient space. */
> -	  if (nbytes_now == 0 && nbytes == 0)
> +	  if (nbytes_now == 0 && nbytes == 0 && is_nonblocking ())
>  	    set_errno (EAGAIN);
> -	  ptr = ((char *) ptr) + chunk;
> +	  ptr = ((char *) ptr) + nbytes_now;
>  	  nbytes += nbytes_now;
> +	  if (reader_closed () && nbytes == 0)
> +	    {
> +	      set_errno(EPIPE);
> +	      raise(SIGPIPE);
> +	    }
> +	  if (!is_nonblocking () && nbytes < len)
> +	    {
> +	      if (nbytes_now == 0)
> +		{
> +		  cygwait (reader_evt);
> +		  ResetEvent (reader_evt);

But what if a read calls SetEvent between cygwait and ResetEvent?
This looks like a potential deadlock issue, no?

> +	    }
>  	}
>        else if (STATUS_PIPE_IS_CLOSED (status))
>  	{
> @@ -430,9 +469,14 @@ fhandler_pipe::raw_write (const void *ptr, size_t len)
>  void
>  fhandler_pipe::fixup_after_fork (HANDLE parent)
>  {
> +  if (close_on_exec() && query_hdl)
> +    CloseHandle (query_hdl);

Why do you close the handle here?  It gets already created with
inheritence settings according to the O_CLOEXEC flag.

>    if (query_hdl)

This is broken.  If you close query_hdl above, it's still != NULL
and fork_fixup will be called.

>      fork_fixup (parent, query_hdl, "query_hdl");
>    fhandler_base::fixup_after_fork (parent);
> +  if (!close_on_exec ())
> +    DuplicateHandle (parent, reader_evt, GetCurrentProcess (), &reader_evt,
> +		     0, 1, DUPLICATE_SAME_ACCESS);

Uhm... this is fixup_after_fork.  I'm a bit puzzled.  You create the
event object with inheritence set to TRUE unconditionally.  So the
forked process will have this handle anyway.  If you duplicate the
handle here, you'll have a handle leak.  What about creating and
duplicating with inheritance == !(flags & O_CLOEXEC) and just call
fork_fixup?

> @@ -463,7 +510,11 @@ fhandler_pipe::close ()
>  {
>    if (query_hdl)
>      NtClose (query_hdl);
> -  return fhandler_base::close ();
> +  int ret = fhandler_base::close ();
> +  if (get_device () == FH_PIPER)
> +    SetEvent (reader_evt);
> +  CloseHandle (reader_evt);
> +  return ret;
>  }

What do you do if the reader process gets killed or crashes?  I fear
this solution has the same problem as a solution using a self-implemented
counter.


Corinna


More information about the Cygwin-developers mailing list