@@ -215,11 +215,10 @@ def _warn_on_consolidation_timeout(self, consolidate_citations: bool) -> None:
215215 f"(2-3 minutes is recommended)."
216216 )
217217
218- def _handle_server_busy_retry (self , file_path : str , retry_func : Any , * args : Any , ** kwargs : Any ) -> Any :
219- """Handle server busy ( 503) retry logic ."""
218+ def _wait_for_busy_server (self , file_path : str ) -> None :
219+ """Pause before re-posting a document the server answered 503 to ."""
220220 self .logger .warning (f"Server busy (503), retrying { file_path } after { self .config ['sleep_time' ]} seconds" )
221221 time .sleep (self .config ["sleep_time" ])
222- return retry_func (* args , ** kwargs )
223222
224223 def _handle_request_error (
225224 self , file_path : str , error : Exception , error_type : str = "Request"
@@ -1824,89 +1823,77 @@ def _send_pdf(
18241823 start : int = - 1 ,
18251824 end : int = - 1
18261825 ) -> Tuple [str , int , Optional [str ]]:
1827- """Post one document, retrying it from a fresh stream on a 503."""
1828- pdf_handle : Optional [BinaryIO ] = None
1829- try :
1830- pdf_handle = open_handle ()
1826+ """Post one document, re-posting it from a fresh stream while the server answers 503.
18311827
1832- files = {
1833- "input" : (
1834- name ,
1835- pdf_handle ,
1836- "application/pdf" ,
1837- {"Expires" : "0" },
1838- )
1839- }
1840-
1841- the_url = self .get_server_url (service )
1842-
1843- # set the GROBID parameters
1844- the_data = {}
1845- if generate_ids :
1846- the_data ["generateIDs" ] = "1"
1847- if consolidate_header :
1848- the_data ["consolidateHeader" ] = "1"
1849- if consolidate_citations :
1850- the_data ["consolidateCitations" ] = "1"
1851- if include_raw_citations :
1852- the_data ["includeRawCitations" ] = "1"
1853- if include_raw_affiliations :
1854- the_data ["includeRawAffiliations" ] = "1"
1855- if tei_coordinates :
1856- the_data ["teiCoordinates" ] = self .config ["coordinates" ]
1857- if segment_sentences :
1858- the_data ["segmentSentences" ] = "1"
1859- if flavor :
1860- the_data ["flavor" ] = flavor
1861- if start and start > 0 :
1862- the_data ["start" ] = str (start )
1863- if end and end > 0 :
1864- the_data ["end" ] = str (end )
1865-
1866- res , status = self .post (
1867- url = the_url , files = files , data = the_data , headers = {"Accept" : "text/plain" },
1868- timeout = self .config ['timeout' ]
1869- )
1828+ A loop rather than a recursive call: every attempt opens, posts and
1829+ closes its own stream before the wait, so a server that stays busy
1830+ costs nothing but time. (Recursing kept one open file per attempt
1831+ and ended in exhausted descriptors or a RecursionError, recorded as
1832+ a bogus error marker.)
1833+ """
1834+ the_url = self .get_server_url (service )
18701835
1871- if status == 503 :
1872- return self ._handle_server_busy_retry (
1873- name ,
1874- self ._send_pdf ,
1875- service ,
1876- name ,
1877- open_handle ,
1878- generate_ids ,
1879- consolidate_header ,
1880- consolidate_citations ,
1881- include_raw_citations ,
1882- include_raw_affiliations ,
1883- tei_coordinates ,
1884- segment_sentences ,
1885- flavor ,
1886- start ,
1887- end
1836+ # set the GROBID parameters
1837+ the_data = {}
1838+ if generate_ids :
1839+ the_data ["generateIDs" ] = "1"
1840+ if consolidate_header :
1841+ the_data ["consolidateHeader" ] = "1"
1842+ if consolidate_citations :
1843+ the_data ["consolidateCitations" ] = "1"
1844+ if include_raw_citations :
1845+ the_data ["includeRawCitations" ] = "1"
1846+ if include_raw_affiliations :
1847+ the_data ["includeRawAffiliations" ] = "1"
1848+ if tei_coordinates :
1849+ the_data ["teiCoordinates" ] = self .config ["coordinates" ]
1850+ if segment_sentences :
1851+ the_data ["segmentSentences" ] = "1"
1852+ if flavor :
1853+ the_data ["flavor" ] = flavor
1854+ if start and start > 0 :
1855+ the_data ["start" ] = str (start )
1856+ if end and end > 0 :
1857+ the_data ["end" ] = str (end )
1858+
1859+ while True :
1860+ pdf_handle : Optional [BinaryIO ] = None
1861+ try :
1862+ pdf_handle = open_handle ()
1863+ files = {
1864+ "input" : (
1865+ name ,
1866+ pdf_handle ,
1867+ "application/pdf" ,
1868+ {"Expires" : "0" },
1869+ )
1870+ }
1871+ res , status = self .post (
1872+ url = the_url , files = files , data = the_data , headers = {"Accept" : "text/plain" },
1873+ timeout = self .config ['timeout' ]
18881874 )
1875+ # requests' exceptions derive from IOError, so they have to be
1876+ # caught before it or every timeout/connection error would be
1877+ # reported as a failure to open the file (status 400).
1878+ except requests .exceptions .ReadTimeout as e :
1879+ self .logger .error (f"Request timeout for { name } : { str (e )} " )
1880+ return (name , 408 , f"Request timeout: { str (e )} " )
1881+ except requests .exceptions .RequestException as e :
1882+ return self ._handle_request_error (name , e )
1883+ except IOError as e :
1884+ self .logger .error (f"Failed to open PDF file { name } : { str (e )} " )
1885+ return (name , 400 , f"Failed to open file: { str (e )} " )
1886+ except Exception as e :
1887+ return self ._handle_unexpected_error (name , e )
1888+ finally :
1889+ # Always ours to close: the caller's own stream is never kept,
1890+ # only the bytes read out of it.
1891+ if pdf_handle :
1892+ pdf_handle .close ()
18891893
1890- return (name , status , res .text )
1891-
1892- # requests' exceptions derive from IOError, so they have to be caught
1893- # before it or every timeout/connection error would be reported as a
1894- # failure to open the file (status 400).
1895- except requests .exceptions .ReadTimeout as e :
1896- self .logger .error (f"Request timeout for { name } : { str (e )} " )
1897- return (name , 408 , f"Request timeout: { str (e )} " )
1898- except requests .exceptions .RequestException as e :
1899- return self ._handle_request_error (name , e )
1900- except IOError as e :
1901- self .logger .error (f"Failed to open PDF file { name } : { str (e )} " )
1902- return (name , 400 , f"Failed to open file: { str (e )} " )
1903- except Exception as e :
1904- return self ._handle_unexpected_error (name , e )
1905- finally :
1906- # Always ours to close: the caller's own stream is never kept, only
1907- # the bytes read out of it.
1908- if pdf_handle :
1909- pdf_handle .close ()
1894+ if status != 503 :
1895+ return (name , status , res .text )
1896+ self ._wait_for_busy_server (name )
19101897
19111898 def process_documents (
19121899 self ,
@@ -2039,31 +2026,23 @@ def process_txt(
20392026 the_data ["includeRawCitations" ] = "1"
20402027 the_data ["citations" ] = references
20412028
2042- try :
2043- res , status = self .post (
2044- url = the_url , data = the_data , headers = {"Accept" : "application/xml" }
2045- )
2046-
2047- if status == 503 :
2048- return self ._handle_server_busy_retry (
2049- txt_file ,
2050- self .process_txt ,
2051- service ,
2052- txt_file ,
2053- generate_ids ,
2054- consolidate_header ,
2055- consolidate_citations ,
2056- include_raw_citations ,
2057- include_raw_affiliations ,
2058- tei_coordinates ,
2059- segment_sentences
2029+ while True :
2030+ try :
2031+ res , status = self .post (
2032+ url = the_url , data = the_data , headers = {"Accept" : "application/xml" },
2033+ timeout = self .config ['timeout' ]
20602034 )
2061- except requests .exceptions .RequestException as e :
2062- return self ._handle_request_error (txt_file , e )
2063- except Exception as e :
2064- return self ._handle_unexpected_error (txt_file , e )
2035+ except requests .exceptions .ReadTimeout as e :
2036+ self .logger .error (f"Request timeout for { txt_file } : { str (e )} " )
2037+ return (txt_file , 408 , f"Request timeout: { str (e )} " )
2038+ except requests .exceptions .RequestException as e :
2039+ return self ._handle_request_error (txt_file , e )
2040+ except Exception as e :
2041+ return self ._handle_unexpected_error (txt_file , e )
20652042
2066- return (txt_file , status , res .text )
2043+ if status != 503 :
2044+ return (txt_file , status , res .text )
2045+ self ._wait_for_busy_server (txt_file )
20672046
20682047
20692048def main () -> None :
0 commit comments