1414
1515"""Define API Jobs."""
1616
17+ from __future__ import division
18+
19+ import concurrent .futures
1720import copy
1821import re
1922import threading
2023
24+ import requests
2125import six
2226from six .moves import http_client
2327
5054_DONE_STATE = "DONE"
5155_STOPPED_REASON = "stopped"
5256_TIMEOUT_BUFFER_SECS = 0.1
57+ _SERVER_TIMEOUT_MARGIN_SECS = 1.0
5358_CONTAINS_ORDER_BY = re .compile (r"ORDER\s+BY" , re .IGNORECASE )
5459
5560_ERROR_REASON_TO_EXCEPTION = {
@@ -663,7 +668,7 @@ def exists(self, client=None, retry=DEFAULT_RETRY):
663668 else :
664669 return True
665670
666- def reload (self , client = None , retry = DEFAULT_RETRY ):
671+ def reload (self , client = None , retry = DEFAULT_RETRY , timeout = None ):
667672 """API call: refresh job properties via a GET request.
668673
669674 See
@@ -675,6 +680,9 @@ def reload(self, client=None, retry=DEFAULT_RETRY):
675680 ``client`` stored on the current dataset.
676681
677682 retry (google.api_core.retry.Retry): (Optional) How to retry the RPC.
683+ timeout (Optional[float]):
684+ The number of seconds to wait for the underlying HTTP transport
685+ before retrying the HTTP request.
678686 """
679687 client = self ._require_client (client )
680688
@@ -683,7 +691,11 @@ def reload(self, client=None, retry=DEFAULT_RETRY):
683691 extra_params ["location" ] = self .location
684692
685693 api_response = client ._call_api (
686- retry , method = "GET" , path = self .path , query_params = extra_params
694+ retry ,
695+ method = "GET" ,
696+ path = self .path ,
697+ query_params = extra_params ,
698+ timeout = timeout ,
687699 )
688700 self ._set_properties (api_response )
689701
@@ -2994,9 +3006,16 @@ def estimated_bytes_processed(self):
29943006 result = int (result )
29953007 return result
29963008
2997- def done (self , retry = DEFAULT_RETRY ):
3009+ def done (self , retry = DEFAULT_RETRY , timeout = None ):
29983010 """Refresh the job and checks if it is complete.
29993011
3012+ Args:
3013+ retry (Optional[google.api_core.retry.Retry]):
3014+ How to retry the call that retrieves query results.
3015+ timeout (Optional[float]):
3016+ The number of seconds to wait for the underlying HTTP transport
3017+ before retrying the HTTP request.
3018+
30003019 Returns:
30013020 bool: True if the job is complete, False otherwise.
30023021 """
@@ -3007,11 +3026,25 @@ def done(self, retry=DEFAULT_RETRY):
30073026 timeout_ms = None
30083027 if self ._done_timeout is not None :
30093028 # Subtract a buffer for context switching, network latency, etc.
3010- timeout = self ._done_timeout - _TIMEOUT_BUFFER_SECS
3011- timeout = max (min (timeout , 10 ), 0 )
3012- self ._done_timeout -= timeout
3029+ api_timeout = self ._done_timeout - _TIMEOUT_BUFFER_SECS
3030+ api_timeout = max (min (api_timeout , 10 ), 0 )
3031+ self ._done_timeout -= api_timeout
30133032 self ._done_timeout = max (0 , self ._done_timeout )
3014- timeout_ms = int (timeout * 1000 )
3033+ timeout_ms = int (api_timeout * 1000 )
3034+
3035+ # If the server-side processing timeout (timeout_ms) is specified and
3036+ # would be picked as the total request timeout, we want to add a small
3037+ # margin to it - we don't want to timeout the connection just as the
3038+ # server-side processing might have completed, but instead slightly
3039+ # after the server-side deadline.
3040+ # However, if `timeout` is specified, and is shorter than the adjusted
3041+ # server timeout, the former prevails.
3042+ if timeout_ms is not None and timeout_ms > 0 :
3043+ server_timeout_with_margin = timeout_ms / 1000 + _SERVER_TIMEOUT_MARGIN_SECS
3044+ if timeout is not None :
3045+ timeout = min (server_timeout_with_margin , timeout )
3046+ else :
3047+ timeout = server_timeout_with_margin
30153048
30163049 # Do not refresh is the state is already done, as the job will not
30173050 # change once complete.
@@ -3022,13 +3055,14 @@ def done(self, retry=DEFAULT_RETRY):
30223055 project = self .project ,
30233056 timeout_ms = timeout_ms ,
30243057 location = self .location ,
3058+ timeout = timeout ,
30253059 )
30263060
30273061 # Only reload the job once we know the query is complete.
30283062 # This will ensure that fields such as the destination table are
30293063 # correctly populated.
30303064 if self ._query_results .complete :
3031- self .reload (retry = retry )
3065+ self .reload (retry = retry , timeout = timeout )
30323066
30333067 return self .state == _DONE_STATE
30343068
@@ -3132,6 +3166,8 @@ def result(
31323166 exc .message += self ._format_for_exception (self .query , self .job_id )
31333167 exc .query_job = self
31343168 raise
3169+ except requests .exceptions .Timeout as exc :
3170+ six .raise_from (concurrent .futures .TimeoutError , exc )
31353171
31363172 # If the query job is complete but there are no query results, this was
31373173 # special job, such as a DDL query. Return an empty result set to
0 commit comments