Skip to content

Fix receive method to enforce total timeout, preventing excessive wait #88

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

milosivanovic
Copy link

Problem

If multiple iterations of receive() are needed to find an ICMP packet matching the sent packet's ID and sequence, the total timeout (e.g., timeout=0.5s) can be exceeded. The socket's settimeout() applies only to individual recvfrom calls, not their cumulative duration. The original code attempted to track this manually with a time_limit, but the check occurred after packet receipt, allowing the total wait time to grow beyond the specified timeout.

Example

Consider three concurrent ping calls (e.g., via threads) using icmplib.ping(host, count=1, interval=0, timeout=0.5, id=proc_num), targeting fictional hosts HostA, HostB, and HostC with a timeout of 0.5s. Assume response times as seen by icmplib:

  • HostA: 100ms response
  • HostB: No response (timeout)
  • HostC: 200ms response

Starting at T=0, the execution unfolds as follows:

  1. Three separate sockets are created, one per ping instance.
  2. Each instance sends an ICMP request to its respective host (HostA, HostB, HostC).
  3. Each instance calls receive() in icmplib/sockets.py, setting the socket timeout to 0.5s via self._sock.settimeout(0.5). All three block at self._sock.recvfrom(1024).

At T=100ms, HostA responds:

  1. HostA's packet arrives and is received by all three sockets.
  2. The recvfrom call succeeds (100ms < 0.5s). HostA's instance matches the ID/sequence and returns.
  3. HostB and HostC instances don’t match, so they loop back to receive(), starting a new recvfrom with a fresh 0.5s timeout. Total time is now 100ms, but the next wait could push it past 0.5s.

At T=200ms, HostC responds:

  1. HostC's packet arrives and is received by the remaining two sockets (HostB, HostC).
  2. The recvfrom call succeeds (100ms wait from T=100ms to T=200ms < 0.5s). HostC's instance matches and returns.
  3. Total time is now 200ms, but HostB's instance loops again, starting a third recvfrom with another 0.5s timeout. At this point, only 0.3s should remain of the total 0.5s, yet it gets a full 0.5s.

At T=700ms, HostB times out:

  1. HostB's third recvfrom (started at T=200ms) finally times out after 500ms (T=200ms + 0.5s = T=700ms).
  2. The current_time > time_limit check triggers, raising a timeout exception, but this is 200ms overdue (total wait = 700ms vs. intended 500ms).

Summary:

  • HostA: Returns at T=100ms (OK).
  • HostC: Returns at T=200ms (OK, but total time already risks exceeding 0.5s).
  • HostB: Times out at T=700ms (bug: 700ms > 0.5s).
    The bug allows each recvfrom to reset the 0.5s clock, accumulating up to 0.5s per iteration (e.g., 100ms + 100ms + 500ms = 700ms), far exceeding the intended total timeout of 0.5s.

Copy link

@kalvdans kalvdans left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like an improvement! I think we should leave the raising of timeout exception to the OS, i.e. call recvfrom one last time with a timeout of 0.

…hods


- Update sync receive to use max(remaining_time, 0) for socket timeout, matching async behavior
- Allows a final non-blocking read when total timeout is reached
- May accept packets slightly past the timeout if data is immediately available

Co-authored-by: kalvdans <github@kalvdans.no-ip.org>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants