Skip to content

[vxi-11] read function bug corrections - #612

Open
hb020 wants to merge 22 commits into
pyvisa:mainfrom
hb020:main
Open

[vxi-11] read function bug corrections #612
hb020 wants to merge 22 commits into
pyvisa:mainfrom
hb020:main

Conversation

@hb020

@hb020 hb020 commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Why

  • On certain inst.chunk_size values in relation to the content size, a read falls in timeout. This is corrected.
  • Read function:
    • return values (status codes) were incomplete. It is now compliant, and can return all 3 non-error possibilities: success, success_termination_character_read, success_max_count_read, all while taking into account VI_ATTR_SUPPRESS_END_EN
    • count boundary check (was absent)
    • timeout handling (was incomplete)
    • zero length handling (was incomplete)

Tasks

  • Closes VXI-11: END/EOI is discarded when response length is an exact multiple of chunk_size, causing VI_ERROR_TMO #608 (and corrected some spacing problems and removed some trailing spaces from CHANGES.md)
  • Executed black . && isort -c . && flake8 with no errors. -> Is this still valid? It seems outdated.
  • The change is fully covered by automated unit tests. Well, not really. Could do it, but will take a lot of time as the framework for this type of tests is lacking in my setup. (need the keysight tools, likely have to find a Windows machine....) But see below for a test setup.
  • Documented in docs/ as appropriate. -> There was no doc of this bug. I might create another PR to document the gaps between pyvisa-py and the 'de facto standard' NI-Visa in more detail.
  • Added an entry to the CHANGES file

@codecov

codecov Bot commented Aug 7, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.01493% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 43.69%. Comparing base (2c52aaa) to head (6f2c756).

Files with missing lines Patch % Lines
pyvisa_py/tcpip.py 86.20% 3 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #612      +/-   ##
==========================================
+ Coverage   41.73%   43.69%   +1.95%     
==========================================
  Files          29       30       +1     
  Lines        5247     5369     +122     
  Branches      521      528       +7     
==========================================
+ Hits         2190     2346     +156     
+ Misses       3030     2991      -39     
- Partials       27       32       +5     
Flag Coverage Δ
unittests 43.69% <97.01%> (+1.95%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@hb020

hb020 commented Aug 7, 2026

Copy link
Copy Markdown
Contributor Author

About the tests, if it's worth something:

test code:

import pyvisa

def testcase(provider, size: int):
    if provider == '@py':
        print(f"******* provider = 'pyvisa-py', data size = {size} *******")
    else:
        print(f"******* provider = 'NI-VISA', data size = {size} *******")
    rm = pyvisa.ResourceManager(provider)
    inst = rm.open_resource('TCPIP::192.168.7.116::inst5::INSTR')
    inst.timeout = 3000
    data_size = size - 2  # add the space for CR/LF
    myquery = f"longrd? {data_size}"  # this is query to a custom device that can return any length of data, up to 2^32-1 bytes. The data_size does not take into account the CR/LF

    # Measure the exact response length (large chunk_size rules out the bug)
    inst.chunk_size = 10 * 1024 * 1024
    inst.write(myquery)
    L = len(inst.read_raw())
    
    for chunk_size in range(L-9, L+11):
        # do a series of number of bytes less and more
        inst.chunk_size = chunk_size
        inst.write(myquery)
        try:
            inst.read_raw()          # -> fails, reproducibly, initially
        except Exception as e:
            print(f'read_raw() with chunk_size {inst.chunk_size} failed: {e}')
            continue
        print(f'read_raw() with chunk_size {inst.chunk_size} succeeded')
    
    
if __name__ == '__main__':
    testcase('@py', 10)
    testcase('@py', 2000)
    testcase('', 10)
    testcase('', 2000)

Without repair:

python3 issue608test.py
******* provider = 'pyvisa-py', data size = 10 *******
read_raw() with chunk_size 1 failed: VI_ERROR_TMO (-1073807339): Timeout expired before operation completed.
read_raw() with chunk_size 2 failed: VI_ERROR_TMO (-1073807339): Timeout expired before operation completed.
read_raw() with chunk_size 3 succeeded
read_raw() with chunk_size 4 succeeded
read_raw() with chunk_size 5 failed: VI_ERROR_TMO (-1073807339): Timeout expired before operation completed.
read_raw() with chunk_size 6 succeeded
read_raw() with chunk_size 7 succeeded
read_raw() with chunk_size 8 succeeded
read_raw() with chunk_size 9 succeeded
read_raw() with chunk_size 10 failed: VI_ERROR_TMO (-1073807339): Timeout expired before operation completed.
read_raw() with chunk_size 11 succeeded
read_raw() with chunk_size 12 succeeded
read_raw() with chunk_size 13 succeeded
read_raw() with chunk_size 14 succeeded
read_raw() with chunk_size 15 succeeded
read_raw() with chunk_size 16 succeeded
read_raw() with chunk_size 17 succeeded
read_raw() with chunk_size 18 succeeded
read_raw() with chunk_size 19 succeeded
read_raw() with chunk_size 20 succeeded
******* provider = 'pyvisa-py', data size = 2000 *******
read_raw() with chunk_size 1991 succeeded
read_raw() with chunk_size 1992 succeeded
read_raw() with chunk_size 1993 succeeded
read_raw() with chunk_size 1994 succeeded
read_raw() with chunk_size 1995 succeeded
read_raw() with chunk_size 1996 succeeded
read_raw() with chunk_size 1997 succeeded
read_raw() with chunk_size 1998 succeeded
read_raw() with chunk_size 1999 succeeded
read_raw() with chunk_size 2000 failed: VI_ERROR_TMO (-1073807339): Timeout expired before operation completed.
read_raw() with chunk_size 2001 succeeded
read_raw() with chunk_size 2002 succeeded
read_raw() with chunk_size 2003 succeeded
read_raw() with chunk_size 2004 succeeded
read_raw() with chunk_size 2005 succeeded
read_raw() with chunk_size 2006 succeeded
read_raw() with chunk_size 2007 succeeded
read_raw() with chunk_size 2008 succeeded
read_raw() with chunk_size 2009 succeeded
read_raw() with chunk_size 2010 succeeded
******* provider = 'NI-VISA', data size = 10 *******
read_raw() with chunk_size 1 succeeded
read_raw() with chunk_size 2 succeeded
read_raw() with chunk_size 3 succeeded
read_raw() with chunk_size 4 succeeded
read_raw() with chunk_size 5 succeeded
read_raw() with chunk_size 6 succeeded
read_raw() with chunk_size 7 succeeded
read_raw() with chunk_size 8 succeeded
read_raw() with chunk_size 9 succeeded
read_raw() with chunk_size 10 succeeded
read_raw() with chunk_size 11 succeeded
read_raw() with chunk_size 12 succeeded
read_raw() with chunk_size 13 succeeded
read_raw() with chunk_size 14 succeeded
read_raw() with chunk_size 15 succeeded
read_raw() with chunk_size 16 succeeded
read_raw() with chunk_size 17 succeeded
read_raw() with chunk_size 18 succeeded
read_raw() with chunk_size 19 succeeded
read_raw() with chunk_size 20 succeeded
******* provider = 'NI-VISA', data size = 2000 *******
read_raw() with chunk_size 1991 succeeded
read_raw() with chunk_size 1992 succeeded
read_raw() with chunk_size 1993 succeeded
read_raw() with chunk_size 1994 succeeded
read_raw() with chunk_size 1995 succeeded
read_raw() with chunk_size 1996 succeeded
read_raw() with chunk_size 1997 succeeded
read_raw() with chunk_size 1998 succeeded
read_raw() with chunk_size 1999 succeeded
read_raw() with chunk_size 2000 succeeded
read_raw() with chunk_size 2001 succeeded
read_raw() with chunk_size 2002 succeeded
read_raw() with chunk_size 2003 succeeded
read_raw() with chunk_size 2004 succeeded
read_raw() with chunk_size 2005 succeeded
read_raw() with chunk_size 2006 succeeded
read_raw() with chunk_size 2007 succeeded
read_raw() with chunk_size 2008 succeeded
read_raw() with chunk_size 2009 succeeded
read_raw() with chunk_size 2010 succeeded

With repair:

python3 issue608test.py              
******* provider = 'pyvisa-py', data size = 10 *******
read_raw() with chunk_size 1 succeeded
read_raw() with chunk_size 2 succeeded
read_raw() with chunk_size 3 succeeded
read_raw() with chunk_size 4 succeeded
read_raw() with chunk_size 5 succeeded
read_raw() with chunk_size 6 succeeded
read_raw() with chunk_size 7 succeeded
read_raw() with chunk_size 8 succeeded
read_raw() with chunk_size 9 succeeded
read_raw() with chunk_size 10 succeeded
read_raw() with chunk_size 11 succeeded
read_raw() with chunk_size 12 succeeded
read_raw() with chunk_size 13 succeeded
read_raw() with chunk_size 14 succeeded
read_raw() with chunk_size 15 succeeded
read_raw() with chunk_size 16 succeeded
read_raw() with chunk_size 17 succeeded
read_raw() with chunk_size 18 succeeded
read_raw() with chunk_size 19 succeeded
read_raw() with chunk_size 20 succeeded
******* provider = 'pyvisa-py', data size = 2000 *******
read_raw() with chunk_size 1991 succeeded
read_raw() with chunk_size 1992 succeeded
read_raw() with chunk_size 1993 succeeded
read_raw() with chunk_size 1994 succeeded
read_raw() with chunk_size 1995 succeeded
read_raw() with chunk_size 1996 succeeded
read_raw() with chunk_size 1997 succeeded
read_raw() with chunk_size 1998 succeeded
read_raw() with chunk_size 1999 succeeded
read_raw() with chunk_size 2000 succeeded
read_raw() with chunk_size 2001 succeeded
read_raw() with chunk_size 2002 succeeded
read_raw() with chunk_size 2003 succeeded
read_raw() with chunk_size 2004 succeeded
read_raw() with chunk_size 2005 succeeded
read_raw() with chunk_size 2006 succeeded
read_raw() with chunk_size 2007 succeeded
read_raw() with chunk_size 2008 succeeded
read_raw() with chunk_size 2009 succeeded
read_raw() with chunk_size 2010 succeeded
******* provider = 'NI-VISA', data size = 10 *******
read_raw() with chunk_size 1 succeeded
read_raw() with chunk_size 2 succeeded
read_raw() with chunk_size 3 succeeded
read_raw() with chunk_size 4 succeeded
read_raw() with chunk_size 5 succeeded
read_raw() with chunk_size 6 succeeded
read_raw() with chunk_size 7 succeeded
read_raw() with chunk_size 8 succeeded
read_raw() with chunk_size 9 succeeded
read_raw() with chunk_size 10 succeeded
read_raw() with chunk_size 11 succeeded
read_raw() with chunk_size 12 succeeded
read_raw() with chunk_size 13 succeeded
read_raw() with chunk_size 14 succeeded
read_raw() with chunk_size 15 succeeded
read_raw() with chunk_size 16 succeeded
read_raw() with chunk_size 17 succeeded
read_raw() with chunk_size 18 succeeded
read_raw() with chunk_size 19 succeeded
read_raw() with chunk_size 20 succeeded
******* provider = 'NI-VISA', data size = 2000 *******
read_raw() with chunk_size 1991 succeeded
read_raw() with chunk_size 1992 succeeded
read_raw() with chunk_size 1993 succeeded
read_raw() with chunk_size 1994 succeeded
read_raw() with chunk_size 1995 succeeded
read_raw() with chunk_size 1996 succeeded
read_raw() with chunk_size 1997 succeeded
read_raw() with chunk_size 1998 succeeded
read_raw() with chunk_size 1999 succeeded
read_raw() with chunk_size 2000 succeeded
read_raw() with chunk_size 2001 succeeded
read_raw() with chunk_size 2002 succeeded
read_raw() with chunk_size 2003 succeeded
read_raw() with chunk_size 2004 succeeded
read_raw() with chunk_size 2005 succeeded
read_raw() with chunk_size 2006 succeeded
read_raw() with chunk_size 2007 succeeded
read_raw() with chunk_size 2008 succeeded
read_raw() with chunk_size 2009 succeeded
read_raw() with chunk_size 2010 succeeded

@hb020 hb020 changed the title discarded when response length is an exact multiple of chunk_size timeout when response length is an exact multiple of chunk_size Aug 7, 2026

@MatthieuDartiailh MatthieuDartiailh left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks the change looks good. I want to audit other transports before merging in case similar issues exist there too. It may take me a couple of weeks.

@berg

berg commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

I actually ran into this same bug when running some conformance tests against VPP-4.3. I have been putting LLMs to work (perhaps controversial!) trying to smoke out weird conformance/compatibility bugs between pyvisa/pyvisa-py, a Rust server implementation of HiSLIP/VXI-11 I am playing with, and some of the virtual Keysight instruments.

The same bug does exist in the HISLIP transport I believe - I was going to send along a patch for that unless this PR gets to it first.

Comment thread pyvisa_py/tcpip.py Outdated
Comment thread pyvisa_py/tcpip.py
@hb020

hb020 commented Aug 13, 2026

Copy link
Copy Markdown
Contributor Author

@MatthieuDartiailh are you OK if I add more VXI-11 compliance patches in this PR? I have identified and patched the following:

  • count boundary check (was absent)
  • timeout handling (was incomplete)
  • zero length handling (was incomplete)
  • correct return value:
    • success_max_count_read on count
    • success on (RX_END and not suppress_end_en) (the latter condition was absent)
    • success_termination_character_read on RX_CHR (was absent)

.. visible in https://github.com/hb020/pyvisa-py/tree/pr-612-extra

@MatthieuDartiailh

Copy link
Copy Markdown
Member

Tjose sounds fine to add here yes.

@hb020

hb020 commented Aug 13, 2026

Copy link
Copy Markdown
Contributor Author

merged into this PR

@hb020

hb020 commented Aug 13, 2026

Copy link
Copy Markdown
Contributor Author

DONE: Will test this more thoroughly on real HW later today. Is OK.

TODO: I might also try to set up a hislip and socket test setup. I do not have USBTMC nor VICP testbeds.

TODO: I'll add any repairs on those 2 backends (hislip/socket) here, and adapt CHANGES once done.

@hb020

hb020 commented Aug 13, 2026

Copy link
Copy Markdown
Contributor Author

@MatthieuDartiailh can we do the hislip stuff in another PR? I found a quite large load of problems in hislip, and having 3 branches open in 3 different PRs on the same source code files is not helping testing....

@MatthieuDartiailh

Copy link
Copy Markdown
Member

Yes since this PR now address multiple VXI-11 not just the original one it is fine to keep focused on this transport. I will do my best to review it in a timely manner.

@hb020

hb020 commented Aug 13, 2026

Copy link
Copy Markdown
Contributor Author

Adapted the changes list.

Comment thread CHANGES Outdated
Comment thread pyvisa_py/tcpip.py Outdated
Comment on lines +742 to +744
timeout = self._io_timeout # this is derived from from self.timeout
# See if a timeout was set. This is derived from self.timeout, but slightly
# convoluted because the unit test scripts may not have set this correctly.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can you clarify this comment ?

@hb020 hb020 Aug 13, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

OK like it is now?

        # Get the timeout as cleaned up by the upper layers
        timeout = self._io_timeout

        # See if a timeout was really set. 
        # if self.timeout is None, the given timeout was VI_TMO_INFINITE
        # This lookup method is also slightly convoluted because the unit test scripts
        # may not have set this correctly.
        finite_timeout = getattr(self, "timeout", None) is not None

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I dislike the idea of making the code weirder because the existing test is poorly written. Could this be improved ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I'll drill this down, and adapt the test scripts if needed.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

should be good now.

@hb020

hb020 commented Aug 15, 2026

Copy link
Copy Markdown
Contributor Author

by the way, found no chunk size problems with hislip so far (apart from lack of events, and richness of end of message type support)

Sockets looks like it has the same problem, but also NI-Visa chokes on it.

@hb020 hb020 changed the title timeout when response length is an exact multiple of chunk_size [vxi-11] read function bug corrections Aug 19, 2026

@MatthieuDartiailh MatthieuDartiailh left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I have one last concern regarding the handling of infinite timeout.

Comment thread pyvisa_py/tcpip.py
remaining_timeout = timeout - elapsed_ms
if finite_timeout and remaining_timeout <= 0:
return bytes(read_data), StatusCode.error_timeout
chunk_timeout = max(10, remaining_timeout)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

If there is no finite timeout this looks wrong since it will cause useless churn on the read function.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

understand, will look into this

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

pushed correction

Comment thread pyvisa_py/tcpip.py Outdated
if remaining_timeout <= 0:
return bytes(read_data), StatusCode.error_timeout
else:
remaining_timeout = 2**32 - 1 # VI_TMO_INFINITE

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Import it and use it rather than redefining it

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.

VXI-11: END/EOI is discarded when response length is an exact multiple of chunk_size, causing VI_ERROR_TMO

3 participants