From a5b9e60af43b317569638f060a3bd7b6bd999ace Mon Sep 17 00:00:00 2001 From: Trevor McKay Date: Fri, 19 Sep 2025 15:57:12 -0400 Subject: [PATCH 1/2] add http timeout values for general, send, and receive to client --- .../cuopt_sh_client/cuopt_self_host_client.py | 48 +++++++++++++++---- .../cuopt_sh_client/cuopt_sh.py | 14 ++++++ 2 files changed, 52 insertions(+), 10 deletions(-) diff --git a/python/cuopt_self_hosted/cuopt_sh_client/cuopt_self_host_client.py b/python/cuopt_self_hosted/cuopt_sh_client/cuopt_self_host_client.py index 7418516f99..f6aebcb8e1 100644 --- a/python/cuopt_self_hosted/cuopt_sh_client/cuopt_self_host_client.py +++ b/python/cuopt_self_hosted/cuopt_sh_client/cuopt_self_host_client.py @@ -291,6 +291,17 @@ class CuOptServiceSelfHostClient: wildcard is used, the result mime_type will be set to the content_type mime_type of the original request. If not provided, result_type defaults to mime_type.MSGPACK + http_general_timeout: int + The time in seconds that http will wait before timing out + on a general request such as a job status check. Default is 30s. + data_send_timeout: int + The time in seconds that http will wait before timing out + on a problem submission to the server. If set to None, + the http_general_timeout value will be used. Default is None. + result_receive_timeout: int + The time in seconds that http will wait before timing out + on receiving a result from the server. If set to None, + the http_general_timeout value will be used. Default is None. """ # Initialize the CuOptServiceSelfHostClient @@ -306,6 +317,9 @@ def __init__( polling_timeout=600, timeout_exception=True, result_type=mime_type.MSGPACK, + http_general_timeout=30, + data_send_timeout=None, + result_receive_timeout=None, ): self.timeout_exception = timeout_exception self.ip = ip @@ -313,6 +327,20 @@ def __init__( self.only_validate = only_validate self.accept_type = result_type + if not isinstance(http_general_timeout, (int, float)): + http_general_timeout = 30 + self.http_general_timeout = http_general_timeout + self.data_send_timeout = ( + data_send_timeout + if isinstance(data_send_timeout, (int, float)) + else self.http_general_timeout + ) + self.result_receive_timeout = ( + result_receive_timeout + if isinstance(result_receive_timeout, (int, float)) + else self.http_general_timeout + ) + self.protocol = "https" if use_https else "http" self.verify = False if use_https is True: @@ -377,7 +405,7 @@ def _get_logs(self, reqId, logging_callback): verify=self.verify, headers=headers, params=params, - timeout=30, + timeout=self.http_general_timeout, ) # File has not been created yet @@ -404,7 +432,7 @@ def _get_incumbents(self, reqId, incumbent_callback): self.solution_url + f"/{reqId}/incumbents", verify=self.verify, headers=headers, - timeout=30, + timeout=self.http_general_timeout, ) response.raise_for_status() response = self._get_response(response) @@ -513,7 +541,7 @@ def stop_threads(log_t, inc_t, done): self.solution_url + f"/{reqId}", verify=self.verify, headers=headers, - timeout=30, + timeout=self.result_receive_timeout, ) response.raise_for_status() response = self._get_response(response) @@ -594,7 +622,7 @@ def serialize(cuopt_problem_data): data=data, headers=headers, verify=self.verify, - timeout=30, + timeout=self.data_send_timeout, ) response.raise_for_status() log.debug(response.status_code) @@ -853,7 +881,7 @@ def delete(self, id, running=None, queued=None, cached=None): "cached": cached, }, verify=self.verify, - timeout=30, + timeout=self.http_general_timeout, ) response.raise_for_status() log.debug(response.status_code) @@ -887,7 +915,7 @@ def delete_solution(self, id): self.solution_url + f"/{id}", headers=headers, verify=self.verify, - timeout=30, + timeout=self.http_general_timeout, ) response.raise_for_status() log.debug(response.status_code) @@ -898,7 +926,7 @@ def delete_solution(self, id): response = requests.delete( self.log_url + f"/{id}", verify=self.verify, - timeout=30, + timeout=self.http_general_timeout, ) except Exception: pass @@ -938,7 +966,7 @@ def repoll(self, data, response_type="obj", delete_solution=True): self.solution_url + f"/{data}", verify=self.verify, headers=headers, - timeout=30, + timeout=self.result_receive_timeout, ) response.raise_for_status() except requests.exceptions.HTTPError as e: @@ -976,7 +1004,7 @@ def status(self, id): self.request_url + f"/{id}?status", verify=self.verify, headers=headers, - timeout=30, + timeout=self.http_general_timeout, ) response.raise_for_status() except requests.exceptions.HTTPError as e: @@ -1014,7 +1042,7 @@ def upload_solution(self, solution): verify=self.verify, data=data, headers=headers, - timeout=30, + timeout=self.data_send_timeout, ) response.raise_for_status() except requests.exceptions.HTTPError as e: diff --git a/python/cuopt_self_hosted/cuopt_sh_client/cuopt_sh.py b/python/cuopt_self_hosted/cuopt_sh_client/cuopt_sh.py index 56763bcb8a..a7a1e925e2 100755 --- a/python/cuopt_self_hosted/cuopt_sh_client/cuopt_sh.py +++ b/python/cuopt_self_hosted/cuopt_sh_client/cuopt_sh.py @@ -48,6 +48,7 @@ def status(args): self_signed_cert=args.self_signed_cert, timeout_exception=False, result_type=result_types[args.result_type], + http_general_timeout=args.http_timeout, ) try: @@ -73,6 +74,7 @@ def delete_request(args): self_signed_cert=args.self_signed_cert, timeout_exception=False, result_type=result_types[args.result_type], + http_general_timeout=args.http_timeout, ) try: @@ -104,6 +106,7 @@ def upload_solution(args): self_signed_cert=args.self_signed_cert, timeout_exception=False, result_type=result_types[args.result_type], + http_general_timeout=args.http_timeout, ) try: @@ -129,6 +132,7 @@ def delete_solution(args): self_signed_cert=args.self_signed_cert, timeout_exception=False, result_type=result_types[args.result_type], + http_general_timeout=args.http_timeout, ) try: @@ -220,6 +224,7 @@ def read_input_data(i_file): only_validate=args.only_validation, timeout_exception=False, result_type=result_types[args.result_type], + http_general_timeout=args.http_timeout, ) try: @@ -587,6 +592,15 @@ def main(): help="Upload a solution to be cached on the server. The reqId " "returned may be used as an initial solution for VRP.", ) + parser.add_argument( + "-ht", + "--http-timeout", + type=int, + default=30, + help="Timeout in seconds for http requests. May need to be increased " + "for large datasets or slow networks. Default is 30s.", + ) + args = parser.parse_args() set_log_level(levels[args.log_level]) if args.version: From 37a31bc47a68261d178081c5978354477c1b04f3 Mon Sep 17 00:00:00 2001 From: Trevor McKay Date: Fri, 19 Sep 2025 17:26:16 -0400 Subject: [PATCH 2/2] Allow None for client timeouts, make -1 sentinel value for send/receive --- .../cuopt_sh_client/cuopt_self_host_client.py | 27 ++++++++++++------- .../cuopt_sh_client/cuopt_sh.py | 3 ++- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/python/cuopt_self_hosted/cuopt_sh_client/cuopt_self_host_client.py b/python/cuopt_self_hosted/cuopt_sh_client/cuopt_self_host_client.py index f6aebcb8e1..3cbd54829d 100644 --- a/python/cuopt_self_hosted/cuopt_sh_client/cuopt_self_host_client.py +++ b/python/cuopt_self_hosted/cuopt_sh_client/cuopt_self_host_client.py @@ -21,6 +21,7 @@ import time import zlib from enum import Enum +from types import NoneType from uuid import UUID import cuopt_mps_parser @@ -294,14 +295,17 @@ class CuOptServiceSelfHostClient: http_general_timeout: int The time in seconds that http will wait before timing out on a general request such as a job status check. Default is 30s. + Set to None to never timeout. data_send_timeout: int The time in seconds that http will wait before timing out - on a problem submission to the server. If set to None, - the http_general_timeout value will be used. Default is None. + on a problem submission to the server. If set to -1, + the http_general_timeout value will be used. Default is -1. + Set to None to never timeout. result_receive_timeout: int The time in seconds that http will wait before timing out - on receiving a result from the server. If set to None, - the http_general_timeout value will be used. Default is None. + on receiving a result from the server. If set to -1, + the http_general_timeout value will be used. Default is -1. + Set to None to never timeout. """ # Initialize the CuOptServiceSelfHostClient @@ -318,8 +322,8 @@ def __init__( timeout_exception=True, result_type=mime_type.MSGPACK, http_general_timeout=30, - data_send_timeout=None, - result_receive_timeout=None, + data_send_timeout=-1, + result_receive_timeout=-1, ): self.timeout_exception = timeout_exception self.ip = ip @@ -327,17 +331,20 @@ def __init__( self.only_validate = only_validate self.accept_type = result_type - if not isinstance(http_general_timeout, (int, float)): - http_general_timeout = 30 + if not isinstance(http_general_timeout, (NoneType, int, float)): + raise ValueError("Incompatible value for http_general_timeout") + self.http_general_timeout = http_general_timeout self.data_send_timeout = ( data_send_timeout - if isinstance(data_send_timeout, (int, float)) + if isinstance(data_send_timeout, (NoneType, int, float)) + and data_send_timeout != -1 else self.http_general_timeout ) self.result_receive_timeout = ( result_receive_timeout - if isinstance(result_receive_timeout, (int, float)) + if isinstance(result_receive_timeout, (NoneType, int, float)) + and result_receive_timeout != -1 else self.http_general_timeout ) diff --git a/python/cuopt_self_hosted/cuopt_sh_client/cuopt_sh.py b/python/cuopt_self_hosted/cuopt_sh_client/cuopt_sh.py index a7a1e925e2..7193d62f4b 100755 --- a/python/cuopt_self_hosted/cuopt_sh_client/cuopt_sh.py +++ b/python/cuopt_self_hosted/cuopt_sh_client/cuopt_sh.py @@ -598,7 +598,8 @@ def main(): type=int, default=30, help="Timeout in seconds for http requests. May need to be increased " - "for large datasets or slow networks. Default is 30s.", + "for large datasets or slow networks. Default is 30s. " + "Set to None to never timeout.", ) args = parser.parse_args()