karpierz / pcap-ct

Python wrapper for the pcap library.
BSD 3-Clause "New" or "Revised" License
19 stars 4 forks source link

loop() blocks in non-blocking mode #9

Open a-sor opened 2 years ago

a-sor commented 2 years ago

Suppose I want to catch exactly 1 packet during 1 second. I do something like this (simplified):

import pcap

def open(name):
    dev = pcap.pcap(name = name, promisc = True, immediate = True, timeout_ms = 1000)
    dev.setnonblock(True)
    return dev

def read(dev):
    ret = b''

    def _callback(timestamp, pkt, *args):
        nonlocal ret
        ret = pkt

    dev.loop(1, _callback)

    return ret

dev = open('myiface0')
pkt = read(dev)

If the packet doesn't arrive, the call to read hangs, because timeout is ignored in the loop function:

    def loop(self, cnt, callback, *args):
#...
        while True:
            # with nogil:
            n = _pcap_ex.next_ex(self.__pcap, ct.byref(phdr), ct.byref(pkt))
            if n == 0:  # timeout
                continue
#...
uhi22 commented 1 year ago

This finding helped me a lot, to understand that the "non-blocking" seems not fully supported (or my understanding is wrong). Nevertheless, I see it a bit different: The loop function looks like intentionally endless, so from my point of view there is no need to change it. But there is a second "looping" function, the next iterator. And this, I expected to be terminated if no more messages are available. But the next contains the same implementation as shown above: it hangs in endless loop if nothing is received. To fix this, I tried the following: In def init, add a new attribute self.blNonBlock=False. In def setnonblock set this new attribute according to the argument: self.blNonBlock=nonblock. And in the def next, use this new attribute to decide, whether in case of timeout we should just continue (as now) or we „raise StopIteration“:

if n == 0:  # timeout
    if (self.blNonBlock):
        raise StopIteration
    else:
        continue

This seems to work: In default blocking mode, the old, blocking behavior is there, and in non-blocking, the iterator correctly terminates. Is my understanding correct? Shall we create a new issue, to avoid mixing the discussion about "loop" and "next"?

Thanks and have a nice day, Uwe

uhi22 commented 1 year ago

Found out, that, instead "solving" the non-blocking issue like mentioned above, there is a better, a real solution: Do not used the iterators or loop. Use the dispatch(). Example of the fix is available in https://github.com/uhi22/pyPLC/commit/88b60197fb5047546eb0883a97225d21477c0b32. From my PoV, this issue can be closed with this conclusion.