@@ -43,8 +43,19 @@ class SandboxAdapter(HttpAdapter):
4343 Number of seconds to wait between requests to check job status.
4444 """
4545
46- def __init__ (self , uri , auth_token , poll_interval = DEFAULT_POLL_INTERVAL ):
47- # type: (Union[Text, SplitResult], Optional[Text], int) -> None
46+ DEFAULT_MAX_POLLS = 8
47+ """
48+ Maximum number of times to poll for job status before giving up.
49+ """
50+
51+ def __init__ (
52+ self ,
53+ uri ,
54+ auth_token ,
55+ poll_interval = DEFAULT_POLL_INTERVAL ,
56+ max_polls = DEFAULT_MAX_POLLS ,
57+ ):
58+ # type: (Union[Text, SplitResult], Optional[Text], int, int) -> None
4859 """
4960 :param uri:
5061 URI of the node to connect to.
@@ -73,6 +84,13 @@ def __init__(self, uri, auth_token, poll_interval=DEFAULT_POLL_INTERVAL):
7384 (once the node completes the job), but it increases traffic to
7485 the node (which may trip a rate limiter and/or incur additional
7586 costs).
87+
88+ :param max_polls:
89+ Max number of times to poll for job status before giving up.
90+ Must be a positive integer.
91+
92+ This is effectively a timeout setting for asynchronous jobs;
93+ multiply by ``poll_interval`` to get the timeout duration.
7694 """
7795 super (SandboxAdapter , self ).__init__ (uri )
7896
@@ -119,7 +137,7 @@ def __init__(self, uri, auth_token, poll_interval=DEFAULT_POLL_INTERVAL):
119137 raise with_context (
120138 exc =
121139 ValueError (
122- '``poll_interval`` must be >= 1 '
140+ '``poll_interval`` must be > 0 '
123141 '(``exc.context`` has more info).' ,
124142 ),
125143
@@ -128,8 +146,35 @@ def __init__(self, uri, auth_token, poll_interval=DEFAULT_POLL_INTERVAL):
128146 },
129147 )
130148
149+ if not isinstance (max_polls , int ):
150+ raise with_context (
151+ exc =
152+ TypeError (
153+ '``max_polls`` must be an int '
154+ '(``exc.context`` has more info).' ,
155+ ),
156+
157+ context = {
158+ 'max_polls' : max_polls ,
159+ },
160+ )
161+
162+ if max_polls < 1 :
163+ raise with_context (
164+ exc =
165+ ValueError (
166+ '``max_polls`` must be > 0 '
167+ '(``exc.context`` has more info).' ,
168+ ),
169+
170+ context = {
171+ 'max_polls' : max_polls ,
172+ },
173+ )
174+
131175 self .auth_token = auth_token # type: Optional[Text]
132176 self .poll_interval = poll_interval # type: int
177+ self .max_polls = max_polls # type: int
133178
134179 @property
135180 def node_url (self ):
@@ -185,8 +230,27 @@ def _interpret_response(self, response, payload, expected_status):
185230 # Check to see if the request was queued for asynchronous
186231 # execution.
187232 if response .status_code == codes ['accepted' ]:
233+ poll_count = 0
188234 while decoded ['status' ] in (STATUS_QUEUED , STATUS_RUNNING ):
235+ if poll_count >= self .max_polls :
236+ raise with_context (
237+ exc =
238+ BadApiResponse (
239+ '``{command}`` job timed out after {duration} seconds '
240+ '(``exc.context`` has more info).' .format (
241+ command = decoded ['command' ],
242+ duration = self .poll_interval * self .max_polls ,
243+ ),
244+ ),
245+
246+ context = {
247+ 'request' : payload ,
248+ 'response' : decoded ,
249+ },
250+ )
251+
189252 self ._wait_to_poll ()
253+ poll_count += 1
190254
191255 poll_response = self ._send_http_request (
192256 headers = {'Authorization' : self .authorization_header },
0 commit comments