Skip to content

extract(no_eof=True) never returns: EOF is detected but nothing stops the loop #620

Description

@JarryShaw

extract(..., no_eof=True) never returns. EOF is detected correctly — the warning fires — but nothing stops the loop, so the extractor spins past end-of-file constructing protocol objects and emitting log records indefinitely.

Reproduction

import pcapkit
pcapkit.extract('examples/captures/in.pcap', nofile=True)               # returns, frames = 6
pcapkit.extract('examples/captures/in.pcap', nofile=True, no_eof=True)  # never returns

Measured on origin/main (a2be2cc1a), CPython 3.14.7, with faulthandler.dump_traceback_later(6, exit=True):

control: no_eof absent -> returned, frames = 6
subject: no_eof=True   -> Timeout (0:00:06)!
Thread 0x00007f2ec7a53740 [python] (most recent call first):
  File ".../logging/__init__.py", line ??? in makeRecord

Deterministic. Independently reproduced twice with the control passing both times — once by the reporting worker under a 1 GiB RLIMIT_AS cap with a forked child per case, once by me as above. Both forms hang:

call result stack at timeout
extract(cap, nofile=True, no_eof=True) hangs pcapkit/protocols/protocol.py __init__
extract(cap, nofile=True, auto=False, store=False, no_eof=True) then iterate hangs logging/__init__.py makeRecord
both controls without no_eof return / iteration ends, 6 frames

So it is not specific to the auto-run path.

The diagnostic detail that matters

ExtractionWarning: EOF reached is emitted in all four cases, including the two that hang. EOF detection therefore works. What no_eof=True suppresses is the EOFError that stops the loop — and nothing else stops it, so the extractor continues past the end of the file.

It did not hit a 1 GiB address-space cap within 8 seconds, so it spins without fast unbounded allocation. That is consistent with it repeatedly constructing protocol objects over an exhausted stream rather than accumulating a large structure.

Stated as inference, not measurement: the mechanism above is read from the two stacks and the warning, not from the extractor source. Neither the reporting worker nor I traced Extractor's loop to confirm it. The reproduction, the controls and the stacks are measured; the causal story is not.

Why this matters

no_eof is a documented public keyword on pcapkit.extract, so this is reachable by an ordinary caller doing exactly what the signature invites. A hang is worse than an exception: there is no traceback, no output — the reporting worker's first attempt produced zero output because stdout was block-buffered, so it could not even tell which call was stuck.

Notes

  • Found while writing the worked example for the README rewrite (docs(readme): trim to a landing page and convert to Markdown #619). The example was steered away from no_eof rather than shipped against a hanging call, and no library code was touched there.
  • A fix needs to decide what "no EOF" should mean when the stream really is exhausted. Suppressing the error while leaving the loop unbounded cannot be right; the loop needs some other termination — the reader reporting exhaustion, a frame-count limit, or no_eof only suppressing the raise at a point where the loop has already decided to stop.
  • A regression test wants a timeout or a frame cap, since a plain assertion cannot fail a hang.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions