all messages for Emacs-related lists mirrored at yhetil.org
 help / color / mirror / code / Atom feed
From: "J.P." <jp@neverwas.me>
To: Eli Zaretskii <eliz@gnu.org>
Cc: "Mattias Engdegård" <mattiase@acm.org>,
	fernandodemorais.jf@gmail.com, bandali@gnu.org,
	54458@debbugs.gnu.org
Subject: bug#54458: 27.2; erc-dcc-get: Re-entering top level after C stack overflow
Date: Mon, 28 Mar 2022 05:08:56 -0700	[thread overview]
Message-ID: <87r16m46uf.fsf@neverwas.me> (raw)
In-Reply-To: <83a6da9vm8.fsf@gnu.org> (Eli Zaretskii's message of "Mon, 28 Mar 2022 14:14:55 +0300")

[-- Attachment #1: Type: text/plain, Size: 4789 bytes --]

Hi Mattias (and Eli),

Mattias Engdegård <mattiase@acm.org> writes:

> 27 mars 2022 kl. 22.54 skrev Mattias Engdegård <mattiase@acm.org>:
>
>> Not sure where this happens but it looks like it might be erc-dcc-send-filter.
>
> Presumably we should add a note to the documentation of process filter
> functions that they shouldn't be used to send more data to the process. (Not
> sure what to recommend, an idle timer maybe?)

As you highlighted up thread, inhibited sends appeal to
`wait_reading_process_output' to try and shake something loose. I agree
such occasions seem likeliest to trigger "unexpected" filter nesting.

As far as best practices go, I'm not sure how a successful
request-response dialog can happen without participants being able to
react in a timely fashion. If the main worry is stack growth, then
perhaps scheduling something on the event loop with a timer (as you say)
makes the most sense.

I actually tried that (via `run-at-time') in the tinkering detailed
below but still managed to incur "error running timer" messages that
referred to "excessive variable binding." Should I have used something
in the "idle" department instead?

The approach that seemed to "work" mimics the one relied on by ERC's
main client filter, which takes pains to ensure overlapping calls merely
stash the input and bail (via `erc-server-processing-p'). Perhaps a
synchronization primitive especially suited to process filters would
make more sense? (No idea.)

> I'm not going to fix this because I don't know ERC very well and wouldn't be
> able to test it sufficiently, but our ERC maintainers do and can!

You're very generous, and your expertise is much appreciated. (But as
Eli says, "hopefully".)


                              . . .

Hi Fernando,

I've managed to trigger behavior (somewhat) resembling what you've
described. It's quite possible, even likely, that this is total baloney,
but please humor me anyway this one round (unless you spot something
totally egregious, that is). The basic "hypothesis" seems to comport
with Mattias's analysis. It posits that the peer you're connecting to is
misbehaving and engaged in some combination of:

 1. not reading frequently enough amid sends

 2. being too stingy with its read buffer

I'd be great if you could confirm this suspicion by checking if the
"window" portion of the TCP ACK segments containing actual payloads go
to zero after a spell. This can be done with tcpdump or wireshark. A
well behaved peer respecting the protocol should normally have nonzero
windows throughout.

On the client side (Emacs master this time), here is what I observe
after following the steps enumerated further down: There appears to be
another buffer of around 64KB that needs filling before I (the receiver)
encounter the error, which in my case is a "variable binding depth
exceeds max-specpdl-size" message along with an unresponsive UI. For me,
this happens without fail at around 800MB after the TCP window goes to 0
(at around 100MB). Strangely, increasing the value of `max-specpdl-size'
doesn't change things perceptively.

Anyway, the file-writing operation continues for around 200MB and
eventually peters out. But the connection only dies after the sender
closes it normally (having sent everything). The IRC connection of
course PINGs out and is severed by the IRC server. The Emacs receiver
process eventually recovers responsiveness (if you wait long enough).

These are the steps I followed. They require two emacs -Q instances, a
server, and the attached script:

 1. connect to the server and make sure the sender can /whois the
    receiver (have them join the same #chan if necessary)
 
 2. start the script:

    python ./script.py ./some_large_file.bin misbehave
 
 3. on the sender:

    /msg RecvNick ^ADCC SEND some_large_file.bin 2130706433 9899 1234567890^A

    where 1234567890 is the size of the file in bytes and ^A is an
    actual control char
 
 4. on the receiver:

    /dcc get SendNick some_large_file.bin

As mentioned earlier, I've attached a crude experiment (patch) that just
records whether we're currently sending (so receipts can be skipped when
the flag is set). I used the process plist for now, but erc-dcc does
keep a global context-state object called `erc-dcc-entry-data', which I
suppose may be more fitting. The idea is to roll with the punches from a
pathological peer but also (of course) interoperate correctly with an
obedient, protocol-abiding one.

Normal sender
- Sends 1405135128 bytes
- Sees 343051 reports

Aberrant sender
- Sends 1405135128 bytes
- Ignores 238662 + unknown reports

Let me know if anything needs clarifying. And thanks for your patience!


[-- Attachment #2: serve.py --]
[-- Type: application/octet-stream, Size: 2199 bytes --]

"""Hostile DCC-SEND endpoint

Usage: python this_script.py ./blob.bin [active_flag]

With ``active_flag``, don't wait for read receipts before sending the
next hunk.

"""
import sys
import socket
import asyncio

from pathlib import Path


class OnConnect:
    def __init__(self, file, no_recv=False):
        self.file = Path(file)
        self.skip = bool(no_recv)
        print(f"Sending {file!r} ({self.file.stat().st_size} bytes)")
        print("Firehose mode:", "on" if self.skip else "off")

    async def read(self) -> int:
        data = await self.reader.read(1024)
        dlen = len(data)
        print("." if dlen == 4 else f"[{dlen}]", end="", flush=True)
        return dlen

    async def handle(self):
        sent = received = 0
        print(f"Connection from {self.writer.get_extra_info('peername')!r}")

        with self.file.open("rb") as f:
            while chunk := f.read(32768):
                self.writer.write(chunk)
                await self.writer.drain()
                sent += len(chunk)
                if self.skip:
                    continue
                received += await self.read()

            try:
                while g := await asyncio.wait_for(self.read(), timeout=1):
                    received += g
            except asyncio.TimeoutError:
                pass
            print("\nSent %d bytes" % sent)
            print("Saw %d reports" % (received // 4))
            self.writer.close()
            await self.writer.wait_closed()

    def __call__(self, reader, writer):
        self.reader = reader
        self.writer = writer
        return self.handle()


async def main(handler: OnConnect):
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
    sock.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 128)
    sock.bind(("127.0.0.1", 9899))
    server = await asyncio.start_server(handler, sock=sock)
    print(f"Serving on {server.sockets[0].getsockname()}")

    async with server:
        await server.serve_forever()


if __name__ == "__main__":
    try:
        asyncio.run(main(OnConnect(*sys.argv[1:])))
    except KeyboardInterrupt:
        print()

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #3: 0001-EXPERIMENT-regulate-ACK-updates-in-erc-dcc-get-filte.patch --]
[-- Type: text/x-patch, Size: 1123 bytes --]

From 7f9a7fe1a1ea9208841a4ec600ec25e1a464b71d Mon Sep 17 00:00:00 2001
From: "F. Jason Park" <jp@neverwas.me>
Date: Mon, 28 Mar 2022 02:24:43 -0700
Subject: [PATCH] [EXPERIMENT] regulate ACK updates in erc-dcc-get-filter

* lisp/erc/erc-dcc.el (erc-dcc-get-filter): Don't bother sending a
"received so far" receipt if another attempt is in progress.
---
 lisp/erc/erc-dcc.el | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/lisp/erc/erc-dcc.el b/lisp/erc/erc-dcc.el
index cc4143bfa2..aaac2277bf 100644
--- a/lisp/erc/erc-dcc.el
+++ b/lisp/erc/erc-dcc.el
@@ -986,9 +986,10 @@ erc-dcc-get-filter
          'dcc-get-file-too-long
          ?f (file-name-nondirectory (buffer-name)))
         (delete-process proc))
-       (t
-        (process-send-string
-         proc (erc-pack-int received-bytes)))))))
+       ((not (process-get proc :sending))
+        (process-put proc :sending t) ; should maybe use `erc-dcc-entry-data'
+        (process-send-string proc (erc-pack-int received-bytes))
+        (process-put proc :sending nil))))))
 
 
 (defun erc-dcc-get-sentinel (proc _event)
-- 
2.35.1


  reply	other threads:[~2022-03-28 12:08 UTC|newest]

Thread overview: 31+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2022-03-18 22:59 bug#54458: 27.2; erc-dcc-get: Re-entering top level after C stack overflow Fernando de Morais
2022-03-21 14:09 ` J.P.
2022-03-22 13:50   ` Fernando de Morais
2022-03-22 14:36     ` Eli Zaretskii
     [not found]       ` <87tubj1eq4.fsf@gmail.com>
2022-03-27 17:56         ` Eli Zaretskii
2022-03-27 22:09           ` Fernando de Morais
2022-03-27 20:54 ` Mattias Engdegård
2022-03-28  9:23   ` Mattias Engdegård
2022-03-28 11:14     ` Eli Zaretskii
2022-03-28 12:08       ` J.P. [this message]
2022-03-29 15:49         ` Mattias Engdegård
2022-03-29 16:45           ` Eli Zaretskii
2022-03-29 17:47             ` Mattias Engdegård
2022-03-29 19:44               ` J.P.
2022-03-30  4:02                 ` J.P.
     [not found]                 ` <87mth8rst7.fsf@neverwas.me>
2022-03-30 15:28                   ` Mattias Engdegård
2022-03-31 19:18                     ` J.P.
     [not found]                     ` <87sfqygccz.fsf@neverwas.me>
2022-04-03 17:20                       ` Fernando de Morais
2022-04-03 19:46                         ` J.P.
     [not found]                         ` <87wng67xxd.fsf@neverwas.me>
2022-04-10 21:31                           ` J.P.
     [not found]                           ` <875yng39sa.fsf@neverwas.me>
2022-04-11  3:17                             ` J.P.
     [not found]                             ` <87sfqkz4ts.fsf@neverwas.me>
2022-04-25  0:59                               ` Fernando de Morais
     [not found]                               ` <87ilqyrn9s.fsf@gmail.com>
2022-04-25 12:08                                 ` J.P.
     [not found]                                 ` <878rrtz7or.fsf@neverwas.me>
2022-04-29 14:51                                   ` Fernando de Morais
     [not found]                                   ` <87r15guen2.fsf@gmail.com>
2022-04-30 13:39                                     ` J.P.
     [not found]                                     ` <87ilqqy9km.fsf@neverwas.me>
2022-05-04 13:03                                       ` Fernando de Morais
     [not found]                                       ` <87pmkth2lr.fsf@gmail.com>
2022-05-06 13:06                                         ` J.P.
     [not found]                                         ` <874k22zu7s.fsf@neverwas.me>
2022-05-08  1:16                                           ` Fernando de Morais
     [not found]                                           ` <87sfpk4ydj.fsf@gmail.com>
2022-05-11 14:29                                             ` J.P.
     [not found]                                             ` <87ee10xhw3.fsf@neverwas.me>
2022-05-23  1:22                                               ` J.P.
2022-04-01  6:32                   ` J.P.

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=87r16m46uf.fsf@neverwas.me \
    --to=jp@neverwas.me \
    --cc=54458@debbugs.gnu.org \
    --cc=bandali@gnu.org \
    --cc=eliz@gnu.org \
    --cc=fernandodemorais.jf@gmail.com \
    --cc=mattiase@acm.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
Code repositories for project(s) associated with this external index

	https://git.savannah.gnu.org/cgit/emacs.git
	https://git.savannah.gnu.org/cgit/emacs/org-mode.git

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.