Skip to content
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

Add return_on_result flag, to return immediately when a result is received #105

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
## <0.11.0.1>.dev0

- *Replace this line with new entries*
- Added a flag `return_on_result`, which causes an immediate return when a response of type 'result' is received,
instead of waiting until the timeout passes.

## 0.11.0.0

Expand Down
8 changes: 4 additions & 4 deletions MANIFEST.in
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ exclude example.py
exclude .flake8
exclude noxfile.py
exclude mkdocs.yml
exclude __pycache__
global-exclude __pycache__
exclude mypy.ini
exclude *.in
exclude *.txt
exclude *.pyc
exclude *.sw[po]
exclude *~
global-exclude *.pyc
global-exclude *.sw[po]
global-exclude *~

prune tests
prune docs
61 changes: 52 additions & 9 deletions pygdbmi/IoManager.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,13 +74,17 @@ def get_gdb_response(
self,
timeout_sec: float = DEFAULT_GDB_TIMEOUT_SEC,
raise_error_on_timeout: bool = True,
return_on_result: bool = False,
) -> List[Dict]:
"""Get response from GDB, and block while doing so. If GDB does not have any response ready to be read
by timeout_sec, an exception is raised.

Args:
timeout_sec: Maximum time to wait for reponse. Must be >= 0. Will return after
raise_error_on_timeout: Whether an exception should be raised if no response was found after timeout_sec
return_on_result: Whether to return when a response of type 'result' was received.
If return_on_result and raise_error_on_timeout, will raise an exception if no response of
type 'result' was received.

Returns:
List of parsed GDB responses, returned from gdbmiparser.parse_response, with the
Expand All @@ -96,19 +100,30 @@ def get_gdb_response(
timeout_sec = 0

if USING_WINDOWS:
retval = self._get_responses_windows(timeout_sec)
retval = self._get_responses_windows(timeout_sec, return_on_result)
else:
retval = self._get_responses_unix(timeout_sec)
retval = self._get_responses_unix(timeout_sec, return_on_result)

if not retval and raise_error_on_timeout:
raise GdbTimeoutError(
"Did not get response from gdb after %s seconds" % timeout_sec
)

else:
return retval
if (
return_on_result
and raise_error_on_timeout
and not any(response["type"] == "result" for response in retval)
):
raise GdbTimeoutError(
"Did not get a 'result' response from gdb after %s seconds"
% timeout_sec
)

return retval

def _get_responses_windows(self, timeout_sec: float) -> List[Dict]:
def _get_responses_windows(
self, timeout_sec: float, return_on_result: bool
) -> List[Dict]:
"""Get responses on windows. Assume no support for select and use a while loop."""
timeout_time_sec = time.time() + timeout_sec
responses = []
Expand All @@ -130,19 +145,29 @@ def _get_responses_windows(self, timeout_sec: float) -> List[Dict]:
pass

responses += responses_list

if return_on_result and any(
response["type"] == "result" for response in (responses_list or [])
):
break

if timeout_sec == 0:
break

elif responses_list and self._allow_overwrite_timeout_times:
timeout_time_sec = min(
time.time() + self.time_to_check_for_additional_output_sec,
timeout_time_sec,
)

elif time.time() > timeout_time_sec:
break

return responses

def _get_responses_unix(self, timeout_sec: float) -> List[Dict]:
def _get_responses_unix(
self, timeout_sec: float, return_on_result: bool
) -> List[Dict]:
"""Get responses on unix-like system. Use select to wait for output."""
timeout_time_sec = time.time() + timeout_sec
responses = []
Expand Down Expand Up @@ -172,10 +197,19 @@ def _get_responses_unix(self, timeout_sec: float) -> List[Dict]:
responses_list = self._get_responses_list(raw_output, stream)
responses += responses_list

if return_on_result and any(
response["type"] == "result" for response in (responses_list or [])
):
break

if timeout_sec == 0: # just exit immediately
break

elif responses_list and self._allow_overwrite_timeout_times:
elif (
responses_list
and self._allow_overwrite_timeout_times
and not return_on_result
):
# update timeout time to potentially be closer to now to avoid lengthy wait times when nothing is being output by gdb
timeout_time_sec = min(
time.time() + self.time_to_check_for_additional_output_sec,
Expand All @@ -197,7 +231,10 @@ def _get_responses_list(
"""
responses: List[Dict[Any, Any]] = []

(_new_output, self._incomplete_output[stream],) = _buffer_incomplete_responses(
(
_new_output,
self._incomplete_output[stream],
) = _buffer_incomplete_responses(
raw_output, self._incomplete_output.get(stream)
)

Expand Down Expand Up @@ -227,6 +264,7 @@ def write(
mi_cmd_to_write: Union[str, List[str]],
timeout_sec: float = DEFAULT_GDB_TIMEOUT_SEC,
raise_error_on_timeout: bool = True,
return_on_result: bool = False,
read_response: bool = True,
) -> List[Dict]:
"""Write to gdb process. Block while parsing responses from gdb for a maximum of timeout_sec.
Expand All @@ -235,6 +273,9 @@ def write(
mi_cmd_to_write: String to write to gdb. If list, it is joined by newlines.
timeout_sec: Maximum number of seconds to wait for response before exiting. Must be >= 0.
raise_error_on_timeout: If read_response is True, raise error if no response is received
return_on_result: Whether to return when a response of type 'result' was received.
If return_on_result and raise_error_on_timeout, will raise an exception if no response of
type 'result' was received.
read_response: Block and read response. If there is a separate thread running, this can be false, and the reading thread read the output.
Returns:
List of parsed gdb responses if read_response is True, otherwise []
Expand Down Expand Up @@ -282,7 +323,9 @@ def write(

if read_response is True:
return self.get_gdb_response(
timeout_sec=timeout_sec, raise_error_on_timeout=raise_error_on_timeout
timeout_sec=timeout_sec,
raise_error_on_timeout=raise_error_on_timeout,
return_on_result=return_on_result,
)

else:
Expand Down
12 changes: 10 additions & 2 deletions pygdbmi/gdbcontroller.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,20 +110,28 @@ def get_gdb_response(
self,
timeout_sec: float = DEFAULT_GDB_TIMEOUT_SEC,
raise_error_on_timeout: bool = True,
return_on_result: bool = False,
) -> List[Dict]:
"""Get gdb response. See IoManager.get_gdb_response() for details"""
return self.io_manager.get_gdb_response(timeout_sec, raise_error_on_timeout)
return self.io_manager.get_gdb_response(
timeout_sec, raise_error_on_timeout, return_on_result
)

def write(
self,
mi_cmd_to_write: Union[str, List[str]],
timeout_sec: float = DEFAULT_GDB_TIMEOUT_SEC,
raise_error_on_timeout: bool = True,
return_on_result: bool = False,
read_response: bool = True,
) -> List[Dict]:
"""Write command to gdb. See IoManager.write() for details"""
return self.io_manager.write(
mi_cmd_to_write, timeout_sec, raise_error_on_timeout, read_response
mi_cmd_to_write,
timeout_sec,
raise_error_on_timeout,
return_on_result,
read_response,
)

def exit(self) -> None:
Expand Down
17 changes: 17 additions & 0 deletions tests/test_gdbcontroller.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import random
import shutil
import subprocess
import time

import pytest

Expand Down Expand Up @@ -68,6 +69,22 @@ def test_controller() -> None:
assert response["stream"] == "stdout"
assert response["token"] is None

# Verify exits quickly if return_on_result is True
t0 = time.monotonic()
responses = gdbmi.write(["-rubbish"], return_on_result=True)
t1 = time.monotonic()
duration = t1 - t0
assert len(responses) != 0
assert duration < 0.01

# Verify waits for timeout if return_on_result is not set
t0 = time.monotonic()
responses = gdbmi.write(["-rubbish"])
t1 = time.monotonic()
duration = t1 - t0
assert len(responses) != 0
assert duration >= 0.2

responses = gdbmi.write(["-file-list-exec-source-files", "-break-insert main"])
assert len(responses) != 0

Expand Down