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

Ken Brown kbrown@cornell.edu
Thu Sep 2 13:01:02 GMT 2021


On 9/2/2021 4:17 AM, Corinna Vinschen wrote:
> On Sep  1 19:02, Ken Brown wrote:
>> On 9/1/2021 9:52 AM, Corinna Vinschen wrote:
>>> Great idea that.  What we need would be some semaphore upside down.
>>> One that can be used to count items and which is signalled if it's
>>> down to zero.
>>
>> Here's an idea (untested), based on https://stackoverflow.com/questions/6559854/is-there-something-opposite-to-semaphore:
>>
>> We create an ordinary Windows semaphore and use it to count the readers: It
>> starts at 0, we increment it by calling ReleaseSemaphore when a reader is
>> opened (by fhandler_pipe::create, fork/exec, dup), and we decrement it by
>> calling WFSO when a reader closes.  When we decrement it, we test whether
>> it's been reduced to 0.  We do this by calling ReleaseSemaphore and using
>> its lpPreviousCount argument.
>>
>> We also create an event that we can use to make WFMO return during a
>> blocking write.  We signal this event if a reader closes and we've
>> discovered that there are no more readers.  In this case we cancel the
>> pending write [*] and return an error.
>>
>> I'm sure I've overlooked something, but does this seem feasible?
> 
> It could work, but the problem with all these approaches is that they
> are tricky and bound to fail as soon as a process is killed or crashes.
> 
>> [*] I don't know offhand if Windows provides a way to cancel a pending
>> write. If not, we could use query_hdl to drain the pipe.
> 
> There's a CancelIoEx function to cancel all async IO on a handle.
> 
> In a lucid moment tonight, I had another idea.
> 
> First of all, scratch my patch.  Also, revert select to check only
> for WriteQuotaAvailable.
> 
> Next, for sanity, let's assume that non-blocking reads don't change
> WriteQuotaAvailable.  So the only important case is the blocking read,
> which reduces WriteQuotaAvailable by the number of requested bytes.
> 
> Next, fact is, we're only interested in WriteQuotaAvailable > 0.
> And we have a buffersize of 64K.
> 
> We can also safely assume that we only have a very small number of
> readers, typically only one.
> 
> So here's the crazily simple idea:
> 
> What if the readers never request more than, say, 50 or even 25% of the
> available buffer space?  Our buffer is 64K and there's no guarantee that
> any read > PIPE_BUF (== 4K) is atomic anyway.  This can work without
> having to check the other side of the pipe.  Something like this,
> ignoring border cases:
> 
> pipe::create()
> {
>     [...]
>     mutex = CreateMutex();
> }
> 
> pipe::raw_read(char *buf, size_t num_requested)
> {
>    if (blocking)
>      {
>        WFSO(mutex);
>        NtQueryInformationFile(FilePipeLocalInformation);
>        if (!fpli.ReadDataAvailable
> 	  && num_requested > fpli.InboundQuota / 4)
> 	num_requested = fpli.InboundQuota / 4;
>        NtReadFile(pipe, buf, num_requested);
>        ReleaseMutex(mutex);
>      }
> }
> 
> It's not entirely foolproof, but it should fix 99% of the cases.

I like it!

Do you think there's anything we can or should do to avoid a deadlock in the 
rare cases where this fails?  The only thing I can think of immediately is to 
always impose a timeout if select is called with infinite timeout on the write 
side of a pipe, after which we report that the pipe is write ready.  After all, 
we've lived since 2008 with a bug that caused select to *always* report write ready.

Alternatively, we could just wait and see if there's an actual use case in which 
someone encounters a deadlock.

Ken


More information about the Cygwin-developers mailing list