[{"data":1,"prerenderedAt":1696},["ShallowReactive",2],{"doc:\u002Fautomating-reporting-workflows\u002Ferror-handling-and-logging-in-excel-automation\u002Fset-timeouts-on-steps-of-an-excel-report-job":3,"surround:\u002Fautomating-reporting-workflows\u002Ferror-handling-and-logging-in-excel-automation\u002Fset-timeouts-on-steps-of-an-excel-report-job":1662,"ancestors:\u002Fautomating-reporting-workflows\u002Ferror-handling-and-logging-in-excel-automation\u002Fset-timeouts-on-steps-of-an-excel-report-job":1670,"children:\u002Fautomating-reporting-workflows\u002Ferror-handling-and-logging-in-excel-automation\u002Fset-timeouts-on-steps-of-an-excel-report-job":1675,"related:\u002Fautomating-reporting-workflows\u002Ferror-handling-and-logging-in-excel-automation\u002Fset-timeouts-on-steps-of-an-excel-report-job":1676},{"id":4,"title":5,"body":6,"dateModified":1629,"datePublished":1629,"description":1630,"extension":1631,"faq":1632,"keyword":1641,"kind":1642,"meta":1643,"navigation":93,"path":1652,"seo":1653,"slug":1656,"stem":1657,"topics":1658,"type":1660,"__hash__":1661},"docs\u002Fautomating-reporting-workflows\u002Ferror-handling-and-logging-in-excel-automation\u002Fset-timeouts-on-steps-of-an-excel-report-job\u002Findex.md","Set Timeouts on Steps of an Excel Report Job",{"type":7,"value":8,"toc":1617},"minimark",[9,19,24,56,59,63,325,359,560,564,771,794,798,1190,1202,1230,1306,1310,1313,1372,1382,1458,1462,1465,1469,1507,1511,1524,1528,1541,1554,1567,1580,1584,1613],[10,11,12,13,18],"p",{},"A report job that fails is easy to deal with: it raises, the alert goes out, someone looks at it. A job that hangs is worse. It never fails, so nothing alerts; it holds its lock, so the next scheduled run skips or stacks up behind it; and the first anyone hears of it is a manager asking where the morning report is. Hangs almost always come from waiting on something outside the script — an API that accepts the connection and never answers, an SMTP server that stops responding mid-conversation, a LibreOffice conversion stuck on a dialog nobody can see. Python waits forever by default in most of these places. This page puts a time limit on each kind of step, shows what happens when the limit is hit, and adds a final limit around the whole job, testing each one against something deliberately slow. It belongs to ",[14,15,17],"a",{"href":16},"\u002Fautomating-reporting-workflows\u002Ferror-handling-and-logging-in-excel-automation\u002F","Error Handling and Logging in Excel Automation",".",[20,21,23],"h2",{"id":22},"prerequisites","Prerequisites",[25,26,31],"pre",{"className":27,"code":28,"language":29,"meta":30,"style":30},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install requests pandas openpyxl\n","bash","",[32,33,34],"code",{"__ignoreMap":30},[35,36,39,43,47,50,53],"span",{"class":37,"line":38},"line",1,[35,40,42],{"class":41},"sMTad","pip",[35,44,46],{"class":45},"srMev"," install",[35,48,49],{"class":45}," requests",[35,51,52],{"class":45}," pandas",[35,54,55],{"class":45}," openpyxl\n",[10,57,58],{},"The examples run a tiny local HTTP server that takes as long as it is told to before replying, so the timeouts can be demonstrated without depending on a real slow service.",[20,60,62],{"id":61},"step-1-http-calls-always-pass-a-timeout","Step 1 — HTTP calls: always pass a timeout",[25,64,68],{"className":65,"code":66,"language":67,"meta":30,"style":30},"language-python shiki shiki-themes github-light github-dark-high-contrast","import time\nimport requests\n\ndef fetch(delay, timeout=(3.05, 2)):\n    t0 = time.perf_counter()\n    try:\n        r = requests.get(f\"http:\u002F\u002F127.0.0.1:8765\u002Fsales?delay={delay}\", timeout=timeout)\n        return f\"ok {r.json()} in {time.perf_counter() - t0:.1f}s\"\n    except requests.Timeout as e:\n        return f\"{type(e).__name__} after {time.perf_counter() - t0:.1f}s\"\n\nprint(\"fast server:\", fetch(0.2))\nprint(\"slow server:\", fetch(5))\n","python",[32,69,70,80,88,95,127,138,147,188,229,244,283,288,308],{"__ignoreMap":30},[35,71,72,76],{"class":37,"line":38},[35,73,75],{"class":74},"s-kum","import",[35,77,79],{"class":78},"skGVy"," time\n",[35,81,83,85],{"class":37,"line":82},2,[35,84,75],{"class":74},[35,86,87],{"class":78}," requests\n",[35,89,91],{"class":37,"line":90},3,[35,92,94],{"emptyLinePlaceholder":93},true,"\n",[35,96,98,101,105,108,111,114,118,121,124],{"class":37,"line":97},4,[35,99,100],{"class":74},"def",[35,102,104],{"class":103},"s_Opv"," fetch",[35,106,107],{"class":78},"(delay, timeout",[35,109,110],{"class":74},"=",[35,112,113],{"class":78},"(",[35,115,117],{"class":116},"sP0c6","3.05",[35,119,120],{"class":78},", ",[35,122,123],{"class":116},"2",[35,125,126],{"class":78},")):\n",[35,128,130,133,135],{"class":37,"line":129},5,[35,131,132],{"class":78},"    t0 ",[35,134,110],{"class":74},[35,136,137],{"class":78}," time.perf_counter()\n",[35,139,141,144],{"class":37,"line":140},6,[35,142,143],{"class":74},"    try",[35,145,146],{"class":78},":\n",[35,148,150,153,155,158,161,164,168,171,174,177,179,183,185],{"class":37,"line":149},7,[35,151,152],{"class":78},"        r ",[35,154,110],{"class":74},[35,156,157],{"class":78}," requests.get(",[35,159,160],{"class":74},"f",[35,162,163],{"class":45},"\"http:\u002F\u002F127.0.0.1:8765\u002Fsales?delay=",[35,165,167],{"class":166},"sSjpA","{",[35,169,170],{"class":78},"delay",[35,172,173],{"class":166},"}",[35,175,176],{"class":45},"\"",[35,178,120],{"class":78},[35,180,182],{"class":181},"sa561","timeout",[35,184,110],{"class":74},[35,186,187],{"class":78},"timeout)\n",[35,189,191,194,197,200,202,205,207,210,212,215,218,221,224,226],{"class":37,"line":190},8,[35,192,193],{"class":74},"        return",[35,195,196],{"class":74}," f",[35,198,199],{"class":45},"\"ok ",[35,201,167],{"class":166},[35,203,204],{"class":78},"r.json()",[35,206,173],{"class":166},[35,208,209],{"class":45}," in ",[35,211,167],{"class":166},[35,213,214],{"class":78},"time.perf_counter() ",[35,216,217],{"class":74},"-",[35,219,220],{"class":78}," t0",[35,222,223],{"class":74},":.1f",[35,225,173],{"class":166},[35,227,228],{"class":45},"s\"\n",[35,230,232,235,238,241],{"class":37,"line":231},9,[35,233,234],{"class":74},"    except",[35,236,237],{"class":78}," requests.Timeout ",[35,239,240],{"class":74},"as",[35,242,243],{"class":78}," e:\n",[35,245,247,249,251,253,255,258,261,264,266,269,271,273,275,277,279,281],{"class":37,"line":246},10,[35,248,193],{"class":74},[35,250,196],{"class":74},[35,252,176],{"class":45},[35,254,167],{"class":166},[35,256,257],{"class":116},"type",[35,259,260],{"class":78},"(e).",[35,262,263],{"class":116},"__name__",[35,265,173],{"class":166},[35,267,268],{"class":45}," after ",[35,270,167],{"class":166},[35,272,214],{"class":78},[35,274,217],{"class":74},[35,276,220],{"class":78},[35,278,223],{"class":74},[35,280,173],{"class":166},[35,282,228],{"class":45},[35,284,286],{"class":37,"line":285},11,[35,287,94],{"emptyLinePlaceholder":93},[35,289,291,294,296,299,302,305],{"class":37,"line":290},12,[35,292,293],{"class":116},"print",[35,295,113],{"class":78},[35,297,298],{"class":45},"\"fast server:\"",[35,300,301],{"class":78},", fetch(",[35,303,304],{"class":116},"0.2",[35,306,307],{"class":78},"))\n",[35,309,311,313,315,318,320,323],{"class":37,"line":310},13,[35,312,293],{"class":116},[35,314,113],{"class":78},[35,316,317],{"class":45},"\"slow server:\"",[35,319,301],{"class":78},[35,321,322],{"class":116},"5",[35,324,307],{"class":78},[10,326,327,328,331,332,335,336,338,339,343,344,347,348,350,351,354,355,18],{},"Against the local test server, the fast request returns in a fraction of a second and the one that would take five seconds is abandoned after two with a ",[32,329,330],{},"ReadTimeout",". ",[32,333,334],{},"requests"," has no default timeout: without the ",[32,337,182],{}," argument a call to a server that accepts the connection but never replies waits indefinitely. The argument takes two numbers — how long to wait to ",[340,341,342],"em",{},"connect"," and how long to wait between bytes of the ",[340,345,346],{},"response"," — and a value of a few seconds for connecting and somewhat longer for reading suits most reporting APIs. Note that the read timeout is not a limit on the total download time; it is the longest silence allowed. A server that trickles one byte every second will never trip it, which is why step 3 adds an overall limit as well. When the timeout fires, ",[32,349,334],{}," raises ",[32,352,353],{},"requests.Timeout",", which the job can retry or report like any other error — see ",[14,356,358],{"href":357},"\u002Fautomating-reporting-workflows\u002Ferror-handling-and-logging-in-excel-automation\u002Fretry-a-failed-excel-report-job-in-python\u002F","Retry a Failed Excel Report Job in Python",[360,361,369,370,369,374,369,378,369,386,369,395,369,399,369,406,369,410,369,414,369,418,369,422,369,426,369,431,369,436,369,440,369,442,369,445,369,447,369,450,369,453,369,456,369,458,369,462,369,466,369,470,369,472,369,475,369,478,369,482,369,484,369,487,369,489,369,491,369,493,369,496,369,499,369,503,369,505,369,508,369,510,369,512,369,514,369,517,369,520,369,523,369,525,369,528,369,530,369,533,369,535,369,538,369,541,369,545,369,547,369,550,369,552,369,555,369,557],"svg",{"viewBox":362,"role":363,"ariaLabelledBy":364,"xmlns":367,"style":368},"111 0 538 224","img",[365,366],"to-defaults-t","to-defaults-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:538px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[371,372,373],"title",{"id":365},"Where Python waits forever by default",[375,376,377],"desc",{"id":366},"requests and urllib wait indefinitely without a timeout argument, smtplib uses the global socket default which is none, subprocess.run waits for the command to finish, and a thread cannot be stopped from outside. Each needs its own explicit limit.",[379,380],"rect",{"x":381,"y":382,"width":383,"height":384,"fill":385},"111","0","538","224","#ffffff",[379,387],{"x":388,"y":389,"width":390,"height":391,"rx":382,"fill":392,"stroke":393,"style":394},"127.2","16.0","32.0","24.0","#e7ebef","var(--line,#cdd5e6)","stroke-width:1px",[379,396],{"x":397,"y":389,"width":398,"height":391,"rx":382,"fill":392,"stroke":393,"style":394},"159.2","141.1",[400,401,405],"text",{"x":402,"y":403,"style":404},"229.8","33.0","font-size:11px;font-weight:600;fill:var(--muted,#5b6780);text-anchor:middle","A",[379,407],{"x":408,"y":389,"width":409,"height":391,"rx":382,"fill":392,"stroke":393,"style":394},"300.4","157.7",[400,411,413],{"x":412,"y":403,"style":404},"379.2","B",[379,415],{"x":416,"y":389,"width":417,"height":391,"rx":382,"fill":392,"stroke":393,"style":394},"458.0","174.7",[400,419,421],{"x":420,"y":403,"style":404},"545.4","C",[379,423],{"x":388,"y":424,"width":390,"height":425,"rx":382,"fill":392,"stroke":393,"style":394},"40.0","28.0",[400,427,430],{"x":428,"y":429,"style":404},"143.2","59.0","1",[379,432],{"x":397,"y":424,"width":398,"height":425,"rx":382,"fill":433,"stroke":434,"style":435},"#f0f4ff","var(--brand,#5b5cf0)","stroke-width:1.5px",[400,437,439],{"x":402,"y":429,"style":438},"font-size:12px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Call",[379,441],{"x":408,"y":424,"width":409,"height":425,"rx":382,"fill":433,"stroke":434,"style":435},[400,443,444],{"x":412,"y":429,"style":438},"Default",[379,446],{"x":416,"y":424,"width":417,"height":425,"rx":382,"fill":433,"stroke":434,"style":435},[400,448,449],{"x":420,"y":429,"style":438},"Set it with",[379,451],{"x":388,"y":452,"width":390,"height":425,"rx":382,"fill":392,"stroke":393,"style":394},"68.0",[400,454,123],{"x":428,"y":455,"style":404},"87.0",[379,457],{"x":397,"y":452,"width":398,"height":425,"rx":382,"fill":385,"stroke":393,"style":394},[400,459,461],{"x":402,"y":455,"style":460},"font-size:12px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","requests.get \u002F post",[379,463],{"x":408,"y":452,"width":409,"height":425,"rx":382,"fill":464,"stroke":465,"style":435},"#fce9e9","var(--danger,#dc2626)",[400,467,469],{"x":412,"y":455,"style":468},"font-size:12px;font-weight:700;fill:var(--accent-ink,#be185d);text-anchor:middle","waits forever",[379,471],{"x":416,"y":452,"width":417,"height":425,"rx":382,"fill":385,"stroke":393,"style":394},[400,473,474],{"x":420,"y":455,"style":460},"timeout=(connect, read)",[379,476],{"x":388,"y":477,"width":390,"height":425,"rx":382,"fill":392,"stroke":393,"style":394},"96.0",[400,479,481],{"x":428,"y":480,"style":404},"115.0","3",[379,483],{"x":397,"y":477,"width":398,"height":425,"rx":382,"fill":385,"stroke":393,"style":394},[400,485,486],{"x":402,"y":480,"style":460},"smtplib.SMTP",[379,488],{"x":408,"y":477,"width":409,"height":425,"rx":382,"fill":464,"stroke":465,"style":435},[400,490,469],{"x":412,"y":480,"style":468},[379,492],{"x":416,"y":477,"width":417,"height":425,"rx":382,"fill":385,"stroke":393,"style":394},[400,494,495],{"x":420,"y":480,"style":460},"timeout=60",[379,497],{"x":388,"y":498,"width":390,"height":425,"rx":382,"fill":392,"stroke":393,"style":394},"124.0",[400,500,502],{"x":428,"y":501,"style":404},"143.0","4",[379,504],{"x":397,"y":498,"width":398,"height":425,"rx":382,"fill":385,"stroke":393,"style":394},[400,506,507],{"x":402,"y":501,"style":460},"subprocess.run",[379,509],{"x":408,"y":498,"width":409,"height":425,"rx":382,"fill":464,"stroke":465,"style":435},[400,511,469],{"x":412,"y":501,"style":468},[379,513],{"x":416,"y":498,"width":417,"height":425,"rx":382,"fill":385,"stroke":393,"style":394},[400,515,516],{"x":420,"y":501,"style":460},"timeout=120",[379,518],{"x":388,"y":519,"width":390,"height":425,"rx":382,"fill":392,"stroke":393,"style":394},"152.0",[400,521,322],{"x":428,"y":522,"style":404},"171.0",[379,524],{"x":397,"y":519,"width":398,"height":425,"rx":382,"fill":385,"stroke":393,"style":394},[400,526,527],{"x":402,"y":522,"style":460},"sqlite3.connect",[379,529],{"x":408,"y":519,"width":409,"height":425,"rx":382,"fill":385,"stroke":393,"style":394},[400,531,532],{"x":412,"y":522,"style":460},"5 s for a lock",[379,534],{"x":416,"y":519,"width":417,"height":425,"rx":382,"fill":385,"stroke":393,"style":394},[400,536,537],{"x":420,"y":522,"style":460},"timeout=30",[379,539],{"x":388,"y":540,"width":390,"height":425,"rx":382,"fill":392,"stroke":393,"style":394},"180.0",[400,542,544],{"x":428,"y":543,"style":404},"199.0","6",[379,546],{"x":397,"y":540,"width":398,"height":425,"rx":382,"fill":385,"stroke":393,"style":394},[400,548,549],{"x":402,"y":543,"style":460},"a pure Python step",[379,551],{"x":408,"y":540,"width":409,"height":425,"rx":382,"fill":464,"stroke":465,"style":435},[400,553,554],{"x":412,"y":543,"style":468},"cannot be interrupted",[379,556],{"x":416,"y":540,"width":417,"height":425,"rx":382,"fill":385,"stroke":393,"style":394},[400,558,559],{"x":420,"y":543,"style":460},"child process",[20,561,563],{"id":562},"step-2-smtp-and-subprocesses","Step 2 — SMTP and subprocesses",[25,565,567],{"className":65,"code":566,"language":67,"meta":30,"style":30},"import smtplib, subprocess\n\n# smtplib: the timeout applies to the connection and to every command after it\ntry:\n    smtplib.SMTP(\"10.255.255.1\", 25, timeout=2)          # an address that never answers\nexcept (TimeoutError, OSError) as e:\n    print(\"smtp:\", type(e).__name__)\n\n# subprocess: the child is killed when the timeout expires\nt0 = time.perf_counter()\ntry:\n    subprocess.run([\"sleep\", \"30\"], timeout=2, check=True)\nexcept subprocess.TimeoutExpired as e:\n    print(f\"subprocess: TimeoutExpired after {time.perf_counter() - t0:.1f}s for {e.cmd}\")\n",[32,568,569,576,580,586,593,620,643,664,668,673,682,688,722,733],{"__ignoreMap":30},[35,570,571,573],{"class":37,"line":38},[35,572,75],{"class":74},[35,574,575],{"class":78}," smtplib, subprocess\n",[35,577,578],{"class":37,"line":82},[35,579,94],{"emptyLinePlaceholder":93},[35,581,582],{"class":37,"line":90},[35,583,585],{"class":584},"s-wDw","# smtplib: the timeout applies to the connection and to every command after it\n",[35,587,588,591],{"class":37,"line":97},[35,589,590],{"class":74},"try",[35,592,146],{"class":78},[35,594,595,598,601,603,606,608,610,612,614,617],{"class":37,"line":129},[35,596,597],{"class":78},"    smtplib.SMTP(",[35,599,600],{"class":45},"\"10.255.255.1\"",[35,602,120],{"class":78},[35,604,605],{"class":116},"25",[35,607,120],{"class":78},[35,609,182],{"class":181},[35,611,110],{"class":74},[35,613,123],{"class":116},[35,615,616],{"class":78},")          ",[35,618,619],{"class":584},"# an address that never answers\n",[35,621,622,625,628,631,633,636,639,641],{"class":37,"line":140},[35,623,624],{"class":74},"except",[35,626,627],{"class":78}," (",[35,629,630],{"class":116},"TimeoutError",[35,632,120],{"class":78},[35,634,635],{"class":116},"OSError",[35,637,638],{"class":78},") ",[35,640,240],{"class":74},[35,642,243],{"class":78},[35,644,645,648,650,653,655,657,659,661],{"class":37,"line":149},[35,646,647],{"class":116},"    print",[35,649,113],{"class":78},[35,651,652],{"class":45},"\"smtp:\"",[35,654,120],{"class":78},[35,656,257],{"class":116},[35,658,260],{"class":78},[35,660,263],{"class":116},[35,662,663],{"class":78},")\n",[35,665,666],{"class":37,"line":190},[35,667,94],{"emptyLinePlaceholder":93},[35,669,670],{"class":37,"line":231},[35,671,672],{"class":584},"# subprocess: the child is killed when the timeout expires\n",[35,674,675,678,680],{"class":37,"line":246},[35,676,677],{"class":78},"t0 ",[35,679,110],{"class":74},[35,681,137],{"class":78},[35,683,684,686],{"class":37,"line":285},[35,685,590],{"class":74},[35,687,146],{"class":78},[35,689,690,693,696,698,701,704,706,708,710,712,715,717,720],{"class":37,"line":290},[35,691,692],{"class":78},"    subprocess.run([",[35,694,695],{"class":45},"\"sleep\"",[35,697,120],{"class":78},[35,699,700],{"class":45},"\"30\"",[35,702,703],{"class":78},"], ",[35,705,182],{"class":181},[35,707,110],{"class":74},[35,709,123],{"class":116},[35,711,120],{"class":78},[35,713,714],{"class":181},"check",[35,716,110],{"class":74},[35,718,719],{"class":116},"True",[35,721,663],{"class":78},[35,723,724,726,729,731],{"class":37,"line":310},[35,725,624],{"class":74},[35,727,728],{"class":78}," subprocess.TimeoutExpired ",[35,730,240],{"class":74},[35,732,243],{"class":78},[35,734,736,738,740,742,745,747,749,751,753,755,757,760,762,765,767,769],{"class":37,"line":735},14,[35,737,647],{"class":116},[35,739,113],{"class":78},[35,741,160],{"class":74},[35,743,744],{"class":45},"\"subprocess: TimeoutExpired after ",[35,746,167],{"class":166},[35,748,214],{"class":78},[35,750,217],{"class":74},[35,752,220],{"class":78},[35,754,223],{"class":74},[35,756,173],{"class":166},[35,758,759],{"class":45},"s for ",[35,761,167],{"class":166},[35,763,764],{"class":78},"e.cmd",[35,766,173],{"class":166},[35,768,176],{"class":45},[35,770,663],{"class":78},[10,772,773,775,776,778,779,781,782,785,786,789,790,18],{},[32,774,486],{}," takes a ",[32,777,182],{}," that covers connecting and every later command on the connection, so a mail server that stalls halfway through a large attachment is caught too. Without it, the library falls back to the global socket default, which is no timeout at all. ",[32,780,507],{}," with ",[32,783,784],{},"timeout="," waits that long and then kills the child process and raises ",[32,787,788],{},"TimeoutExpired",". This matters most for converting workbooks to PDF with LibreOffice, which can sit forever on a recovery or macro-security dialog that nobody sees in a headless session; a generous limit of a minute or two per file turns that into an error the job can report. When LibreOffice is started with a separate profile directory per run, a killed conversion does not leave a lock behind for the next one, as explained in ",[14,791,793],{"href":792},"\u002Fautomating-reporting-workflows\u002Fexporting-excel-reports-to-pdf\u002Fconvert-excel-to-pdf-on-linux-with-libreoffice\u002F","Convert Excel to PDF on Linux with LibreOffice",[20,795,797],{"id":796},"step-3-a-step-with-no-timeout-option-run-it-in-a-child-process","Step 3 — A step with no timeout option: run it in a child process",[25,799,801],{"className":65,"code":800,"language":67,"meta":30,"style":30},"import multiprocessing as mp\n\ndef slow_transform(path, seconds):\n    import time, pandas as pd\n    time.sleep(seconds)                          # stands in for a step that hangs\n    pd.DataFrame({\"a\": [1, 2]}).to_excel(path, index=False)\n\ndef run_with_limit(target, args, limit):\n    ctx = mp.get_context(\"spawn\")\n    p = ctx.Process(target=target, args=args)\n    p.start()\n    p.join(limit)\n    if p.is_alive():\n        p.terminate()\n        p.join(5)\n        if p.is_alive():\n            p.kill()\n        raise TimeoutError(f\"{target.__name__} did not finish within {limit}s\")\n    if p.exitcode != 0:\n        raise RuntimeError(f\"{target.__name__} exited with code {p.exitcode}\")\n\nif __name__ == \"__main__\":\n    run_with_limit(slow_transform, (\"ok.xlsx\", 0.5), limit=10)\n    print(\"fast step finished\")\n    try:\n        run_with_limit(slow_transform, (\"stuck.xlsx\", 60), limit=3)\n    except TimeoutError as e:\n        print(\"timeout:\", e)\n",[32,802,803,815,819,829,842,850,880,884,894,909,935,940,945,953,958,968,976,982,1021,1037,1073,1078,1095,1121,1133,1140,1164,1176],{"__ignoreMap":30},[35,804,805,807,810,812],{"class":37,"line":38},[35,806,75],{"class":74},[35,808,809],{"class":78}," multiprocessing ",[35,811,240],{"class":74},[35,813,814],{"class":78}," mp\n",[35,816,817],{"class":37,"line":82},[35,818,94],{"emptyLinePlaceholder":93},[35,820,821,823,826],{"class":37,"line":90},[35,822,100],{"class":74},[35,824,825],{"class":103}," slow_transform",[35,827,828],{"class":78},"(path, seconds):\n",[35,830,831,834,837,839],{"class":37,"line":97},[35,832,833],{"class":74},"    import",[35,835,836],{"class":78}," time, pandas ",[35,838,240],{"class":74},[35,840,841],{"class":78}," pd\n",[35,843,844,847],{"class":37,"line":129},[35,845,846],{"class":78},"    time.sleep(seconds)                          ",[35,848,849],{"class":584},"# stands in for a step that hangs\n",[35,851,852,855,858,861,863,865,867,870,873,875,878],{"class":37,"line":140},[35,853,854],{"class":78},"    pd.DataFrame({",[35,856,857],{"class":45},"\"a\"",[35,859,860],{"class":78},": [",[35,862,430],{"class":116},[35,864,120],{"class":78},[35,866,123],{"class":116},[35,868,869],{"class":78},"]}).to_excel(path, ",[35,871,872],{"class":181},"index",[35,874,110],{"class":74},[35,876,877],{"class":116},"False",[35,879,663],{"class":78},[35,881,882],{"class":37,"line":149},[35,883,94],{"emptyLinePlaceholder":93},[35,885,886,888,891],{"class":37,"line":190},[35,887,100],{"class":74},[35,889,890],{"class":103}," run_with_limit",[35,892,893],{"class":78},"(target, args, limit):\n",[35,895,896,899,901,904,907],{"class":37,"line":231},[35,897,898],{"class":78},"    ctx ",[35,900,110],{"class":74},[35,902,903],{"class":78}," mp.get_context(",[35,905,906],{"class":45},"\"spawn\"",[35,908,663],{"class":78},[35,910,911,914,916,919,922,924,927,930,932],{"class":37,"line":246},[35,912,913],{"class":78},"    p ",[35,915,110],{"class":74},[35,917,918],{"class":78}," ctx.Process(",[35,920,921],{"class":181},"target",[35,923,110],{"class":74},[35,925,926],{"class":78},"target, ",[35,928,929],{"class":181},"args",[35,931,110],{"class":74},[35,933,934],{"class":78},"args)\n",[35,936,937],{"class":37,"line":285},[35,938,939],{"class":78},"    p.start()\n",[35,941,942],{"class":37,"line":290},[35,943,944],{"class":78},"    p.join(limit)\n",[35,946,947,950],{"class":37,"line":310},[35,948,949],{"class":74},"    if",[35,951,952],{"class":78}," p.is_alive():\n",[35,954,955],{"class":37,"line":735},[35,956,957],{"class":78},"        p.terminate()\n",[35,959,961,964,966],{"class":37,"line":960},15,[35,962,963],{"class":78},"        p.join(",[35,965,322],{"class":116},[35,967,663],{"class":78},[35,969,971,974],{"class":37,"line":970},16,[35,972,973],{"class":74},"        if",[35,975,952],{"class":78},[35,977,979],{"class":37,"line":978},17,[35,980,981],{"class":78},"            p.kill()\n",[35,983,985,988,991,993,995,997,999,1002,1004,1006,1009,1011,1014,1016,1019],{"class":37,"line":984},18,[35,986,987],{"class":74},"        raise",[35,989,990],{"class":116}," TimeoutError",[35,992,113],{"class":78},[35,994,160],{"class":74},[35,996,176],{"class":45},[35,998,167],{"class":166},[35,1000,1001],{"class":78},"target.",[35,1003,263],{"class":116},[35,1005,173],{"class":166},[35,1007,1008],{"class":45}," did not finish within ",[35,1010,167],{"class":166},[35,1012,1013],{"class":78},"limit",[35,1015,173],{"class":166},[35,1017,1018],{"class":45},"s\"",[35,1020,663],{"class":78},[35,1022,1024,1026,1029,1032,1035],{"class":37,"line":1023},19,[35,1025,949],{"class":74},[35,1027,1028],{"class":78}," p.exitcode ",[35,1030,1031],{"class":74},"!=",[35,1033,1034],{"class":116}," 0",[35,1036,146],{"class":78},[35,1038,1040,1042,1045,1047,1049,1051,1053,1055,1057,1059,1062,1064,1067,1069,1071],{"class":37,"line":1039},20,[35,1041,987],{"class":74},[35,1043,1044],{"class":116}," RuntimeError",[35,1046,113],{"class":78},[35,1048,160],{"class":74},[35,1050,176],{"class":45},[35,1052,167],{"class":166},[35,1054,1001],{"class":78},[35,1056,263],{"class":116},[35,1058,173],{"class":166},[35,1060,1061],{"class":45}," exited with code ",[35,1063,167],{"class":166},[35,1065,1066],{"class":78},"p.exitcode",[35,1068,173],{"class":166},[35,1070,176],{"class":45},[35,1072,663],{"class":78},[35,1074,1076],{"class":37,"line":1075},21,[35,1077,94],{"emptyLinePlaceholder":93},[35,1079,1081,1084,1087,1090,1093],{"class":37,"line":1080},22,[35,1082,1083],{"class":74},"if",[35,1085,1086],{"class":116}," __name__",[35,1088,1089],{"class":74}," ==",[35,1091,1092],{"class":45}," \"__main__\"",[35,1094,146],{"class":78},[35,1096,1098,1101,1104,1106,1109,1112,1114,1116,1119],{"class":37,"line":1097},23,[35,1099,1100],{"class":78},"    run_with_limit(slow_transform, (",[35,1102,1103],{"class":45},"\"ok.xlsx\"",[35,1105,120],{"class":78},[35,1107,1108],{"class":116},"0.5",[35,1110,1111],{"class":78},"), ",[35,1113,1013],{"class":181},[35,1115,110],{"class":74},[35,1117,1118],{"class":116},"10",[35,1120,663],{"class":78},[35,1122,1124,1126,1128,1131],{"class":37,"line":1123},24,[35,1125,647],{"class":116},[35,1127,113],{"class":78},[35,1129,1130],{"class":45},"\"fast step finished\"",[35,1132,663],{"class":78},[35,1134,1136,1138],{"class":37,"line":1135},25,[35,1137,143],{"class":74},[35,1139,146],{"class":78},[35,1141,1143,1146,1149,1151,1154,1156,1158,1160,1162],{"class":37,"line":1142},26,[35,1144,1145],{"class":78},"        run_with_limit(slow_transform, (",[35,1147,1148],{"class":45},"\"stuck.xlsx\"",[35,1150,120],{"class":78},[35,1152,1153],{"class":116},"60",[35,1155,1111],{"class":78},[35,1157,1013],{"class":181},[35,1159,110],{"class":74},[35,1161,481],{"class":116},[35,1163,663],{"class":78},[35,1165,1167,1169,1171,1174],{"class":37,"line":1166},27,[35,1168,234],{"class":74},[35,1170,990],{"class":116},[35,1172,1173],{"class":74}," as",[35,1175,243],{"class":78},[35,1177,1179,1182,1184,1187],{"class":37,"line":1178},28,[35,1180,1181],{"class":116},"        print",[35,1183,113],{"class":78},[35,1185,1186],{"class":45},"\"timeout:\"",[35,1188,1189],{"class":78},", e)\n",[25,1191,1193],{"className":27,"code":1192,"language":29,"meta":30,"style":30},"python step_limits.py\n",[32,1194,1195],{"__ignoreMap":30},[35,1196,1197,1199],{"class":37,"line":38},[35,1198,67],{"class":41},[35,1200,1201],{"class":45}," step_limits.py\n",[10,1203,1204,1205,1208,1209,1212,1213,1216,1217,1220,1221,1224,1225,1227,1228,18],{},"Some steps have no timeout setting at all: a long pandas operation, a third-party library that blocks inside C code, a database driver without a statement timeout. A thread does not help here, because Python has no way to stop a thread from outside — it keeps running after you stop waiting for it. A separate process can be stopped: run the step in a child process, wait for it with ",[32,1206,1207],{},"join(limit)",", and if it is still alive, ",[32,1210,1211],{},"terminate()"," it (and ",[32,1214,1215],{},"kill()"," it if it ignores that). The ",[32,1218,1219],{},"spawn"," start method gives the child a clean interpreter, which is the default on Windows and macOS and the safest choice everywhere. The cost is that the step's inputs and outputs have to cross the process boundary, so this pattern suits steps that read a file and write a file, like the one above. The ",[32,1222,1223],{},"if __name__ == \"__main__\":"," guard is required with ",[32,1226,1219],{},", because the child imports the main module; for the same reason the step function must be defined at module level in a file, not inside another function or typed into an interactive session. Run as a script, the fast step finishes normally and the stuck one is stopped after three seconds with a clear ",[32,1229,630],{},[360,1231,369,1237,369,1240,369,1243,369,1247,369,1254,369,1260,369,1265,369,1269,369,1273,369,1277,369,1281,369,1286,369,1291,369,1295,369,1297,369,1300,369,1303],{"viewBox":1232,"role":363,"ariaLabelledBy":1233,"xmlns":367,"style":1236},"4 0 752 199",[1234,1235],"to-thread-vs-proc-t","to-thread-vs-proc-d","width:100%;max-width:752px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[371,1238,1239],{"id":1234},"Why a thread cannot enforce a timeout",[375,1241,1242],{"id":1235},"With a thread, the main program can stop waiting after the limit but the stuck work keeps running and holding resources. With a child process, the main program can terminate the process after the limit and all its resources are released.",[379,1244],{"x":502,"y":382,"width":1245,"height":1246,"fill":385},"752","199",[379,1248],{"x":1249,"y":389,"width":1250,"height":1251,"rx":1252,"fill":464,"stroke":465,"style":1253},"20.0","351.0","167.0","14","stroke-width:2px",[400,1255,1259],{"x":1256,"y":1257,"style":1258},"195.5","44.0","font-size:14px;font-weight:700;fill:var(--accent-ink,#be185d);text-anchor:middle","Thread",[37,1261],{"x1":1262,"y1":1263,"x2":1264,"y2":1263,"stroke":465,"style":394},"36.0","57.0","355.0",[400,1266,1268],{"x":1256,"y":1267,"style":460},"77.0","join(limit) stops waiting",[400,1270,1272],{"x":1256,"y":1271,"style":460},"103.0","the work keeps running",[400,1274,1276],{"x":1256,"y":1275,"style":460},"129.0","files and memory stay held",[400,1278,1280],{"x":1256,"y":1279,"style":460},"155.0","no safe way to stop it",[379,1282],{"x":1283,"y":389,"width":1250,"height":1251,"rx":1252,"fill":1284,"stroke":1285,"style":1253},"389.0","#d9f4f1","var(--teal,#0f9488)",[400,1287,1290],{"x":1288,"y":1257,"style":1289},"564.5","font-size:14px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","Child process",[37,1292],{"x1":1293,"y1":1263,"x2":1294,"y2":1263,"stroke":1285,"style":394},"405.0","724.0",[400,1296,1268],{"x":1288,"y":1267,"style":460},[400,1298,1299],{"x":1288,"y":1271,"style":460},"terminate() ends the work",[400,1301,1302],{"x":1288,"y":1275,"style":460},"the OS frees its resources",[400,1304,1305],{"x":1288,"y":1279,"style":460},"exit code says what happened",[20,1307,1309],{"id":1308},"step-4-an-overall-limit-for-the-whole-job","Step 4 — An overall limit for the whole job",[10,1311,1312],{},"The step limits handle the expected waits; a final limit around the whole job catches everything else — a step without a limit that someone adds next year, a server that trickles data slowly enough to stay under the read timeout. Put it in the scheduler, where it works even if the Python process itself is stuck:",[25,1314,1316],{"className":27,"code":1315,"language":29,"meta":30,"style":30},"# cron with coreutils timeout: SIGTERM after 30 minutes, SIGKILL 60 seconds later\n0 6 * * 1-5  timeout --kill-after=60s 30m \u002Fopt\u002Freports\u002F.venv\u002Fbin\u002Fpython \u002Fopt\u002Freports\u002Fdaily_sales.py\n\n# systemd service: the same limit as a unit setting\n# [Service]\n# TimeoutStartSec=30min\n",[32,1317,1318,1323,1353,1357,1362,1367],{"__ignoreMap":30},[35,1319,1320],{"class":37,"line":38},[35,1321,1322],{"class":584},"# cron with coreutils timeout: SIGTERM after 30 minutes, SIGKILL 60 seconds later\n",[35,1324,1325,1327,1330,1333,1335,1338,1341,1344,1347,1350],{"class":37,"line":82},[35,1326,382],{"class":41},[35,1328,1329],{"class":116}," 6",[35,1331,1332],{"class":116}," *",[35,1334,1332],{"class":116},[35,1336,1337],{"class":45}," 1-5",[35,1339,1340],{"class":45},"  timeout",[35,1342,1343],{"class":116}," --kill-after=60s",[35,1345,1346],{"class":45}," 30m",[35,1348,1349],{"class":45}," \u002Fopt\u002Freports\u002F.venv\u002Fbin\u002Fpython",[35,1351,1352],{"class":45}," \u002Fopt\u002Freports\u002Fdaily_sales.py\n",[35,1354,1355],{"class":37,"line":90},[35,1356,94],{"emptyLinePlaceholder":93},[35,1358,1359],{"class":37,"line":97},[35,1360,1361],{"class":584},"# systemd service: the same limit as a unit setting\n",[35,1363,1364],{"class":37,"line":129},[35,1365,1366],{"class":584},"# [Service]\n",[35,1368,1369],{"class":37,"line":140},[35,1370,1371],{"class":584},"# TimeoutStartSec=30min\n",[10,1373,1374,1375,1378,1379,1381],{},"On Windows, Task Scheduler's ",[340,1376,1377],{},"Stop the task if it runs longer than"," setting does the same. Choose the limit from the job's history — about twice its normal run time is a reasonable start — so it only fires when something is really wrong. A job killed this way exits with a non-zero status (124 for ",[32,1380,182],{},"), so the usual failure alert still goes out, and it releases its lock so the next run can start.",[360,1383,369,1389,369,1392,369,1395,369,1400,369,1405,369,1411,369,1416,369,1420,369,1425,369,1430,369,1435,369,1439,369,1443,369,1446,369,1449,369,1454],{"viewBox":1384,"role":363,"ariaLabelledBy":1385,"xmlns":367,"style":1388},"84 0 592 281",[1386,1387],"to-layers-t","to-layers-d","width:100%;max-width:592px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[371,1390,1391],{"id":1386},"Three layers of time limits",[375,1393,1394],{"id":1387},"Each call to an outside system has its own timeout. Steps with no timeout option run in a child process that can be terminated. The whole job has an overall limit enforced by the scheduler.",[379,1396],{"x":1397,"y":382,"width":1398,"height":1399,"fill":385},"84","592","281",[379,1401],{"x":1402,"y":389,"width":1403,"height":429,"rx":1404,"fill":433,"stroke":434,"style":1253},"100.0","560.0","12",[400,1406,1410],{"x":1407,"y":1408,"style":1409},"380.0","41.5","font-size:13.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Per call",[400,1412,1415],{"x":1407,"y":1413,"style":1414},"61.0","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","requests, smtplib, subprocess",[37,1417],{"x1":1407,"y1":1418,"x2":1407,"y2":1419,"stroke":434,"style":1253},"80.0","102.0",[1421,1422],"polygon",{"points":1423,"fill":1424},"380.0,106.0 375.0,97.0 385.0,97.0","#5b5cf0",[379,1426],{"x":1402,"y":1427,"width":1403,"height":429,"rx":1404,"fill":1428,"stroke":1429,"style":1253},"111.0","#fdefd8","var(--gold,#b4740a)",[400,1431,1434],{"x":1407,"y":1432,"style":1433},"136.5","font-size:13.5px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","Per step",[400,1436,1438],{"x":1407,"y":1437,"style":1414},"156.0","child process with join(limit)",[37,1440],{"x1":1407,"y1":1441,"x2":1407,"y2":1442,"stroke":434,"style":1253},"175.0","197.0",[1421,1444],{"points":1445,"fill":1424},"380.0,201.0 375.0,192.0 385.0,192.0",[379,1447],{"x":1402,"y":1448,"width":1403,"height":429,"rx":1404,"fill":1284,"stroke":1285,"style":1253},"206.0",[400,1450,1453],{"x":1407,"y":1451,"style":1452},"231.5","font-size:13.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","Per job",[400,1455,1457],{"x":1407,"y":1456,"style":1414},"251.0","timeout command or scheduler",[20,1459,1461],{"id":1460},"choosing-the-numbers","Choosing the numbers",[10,1463,1464],{},"A timeout that is too short creates failures where there were none, and one that is too long lets a hang eat into the time left before the deadline. The run history is the best guide: if fetching data from an API normally takes four seconds and has never taken more than fifteen, a read timeout of thirty seconds catches hangs without false alarms. Combine timeouts with a small number of retries for calls that are safe to repeat, and make sure the retries plus their timeouts still fit inside the job's overall limit — three retries of a two-minute timeout is six minutes, which a thirty-minute job can absorb but a five-minute one cannot.",[20,1466,1468],{"id":1467},"common-pitfalls","Common pitfalls",[1470,1471,1472,1483,1489,1495,1501],"ul",{},[1473,1474,1475,1482],"li",{},[1476,1477,1478,1479,1481],"strong",{},"No ",[32,1480,784],{}," on requests."," The default is to wait forever.",[1473,1484,1485,1488],{},[1476,1486,1487],{},"Treating the read timeout as a total limit."," It is the longest silence; add an overall limit.",[1473,1490,1491,1494],{},[1476,1492,1493],{},"Threads for timeouts."," A thread cannot be stopped; use a child process for stuck steps.",[1473,1496,1497,1500],{},[1476,1498,1499],{},"No limit in the scheduler."," It is the only one that works when the process itself hangs.",[1473,1502,1503,1506],{},[1476,1504,1505],{},"Retries that exceed the deadline."," Retries × timeout must fit within the job limit.",[20,1508,1510],{"id":1509},"conclusion","Conclusion",[10,1512,1513,1514,1517,1518,1520,1521,1523],{},"Hangs are worse than failures because nothing reports them. Give every HTTP call a ",[32,1515,1516],{},"(connect, read)"," timeout, every SMTP connection a ",[32,1519,784],{},", and every subprocess a ",[32,1522,784],{}," so LibreOffice and other tools are killed when stuck; run steps that cannot be interrupted in a child process that can be terminated; and put an overall limit on the job in cron, systemd or Task Scheduler.",[20,1525,1527],{"id":1526},"frequently-asked-questions","Frequently asked questions",[10,1529,1530,1533,1534,1536,1537,1540],{},[1476,1531,1532],{},"Does requests have a default timeout?","\nNo; without ",[32,1535,784],{}," a request can wait forever. Pass a tuple such as ",[32,1538,1539],{},"(3.05, 30)"," for connect and read timeouts.",[10,1542,1543,1546,1547,1550,1551,1553],{},[1476,1544,1545],{},"How do I stop a subprocess that hangs?","\nUse ",[32,1548,1549],{},"subprocess.run(..., timeout=seconds)","; when the time is up, the child is killed and ",[32,1552,788],{}," is raised.",[10,1555,1556,1559,1560,1563,1564,1566],{},[1476,1557,1558],{},"How can I put a time limit on a Python function?","\nRun it in a ",[32,1561,1562],{},"multiprocessing.Process",", call ",[32,1565,1207],{},", and terminate the process if it is still alive; threads cannot be stopped from outside.",[10,1568,1569,1572,1573,1575,1576,1579],{},[1476,1570,1571],{},"How do I limit the total run time of a cron job?","\nWrap the command in ",[32,1574,182],{},", for example ",[32,1577,1578],{},"timeout --kill-after=60s 30m python job.py",", or set a time limit in systemd or Task Scheduler.",[20,1581,1583],{"id":1582},"related","Related",[1470,1585,1586,1592,1599,1606],{},[1473,1587,1588,1589,1591],{},"Up one level: ",[14,1590,17],{"href":16}," — robust report jobs.",[1473,1593,1594,1598],{},[14,1595,1597],{"href":1596},"\u002Fautomating-reporting-workflows\u002Ferror-handling-and-logging-in-excel-automation\u002Fprevent-overlapping-runs-of-an-excel-report-job\u002F","Prevent Overlapping Runs of an Excel Report Job"," — locks between runs.",[1473,1600,1601,1605],{},[14,1602,1604],{"href":1603},"\u002Fautomating-reporting-workflows\u002Ferror-handling-and-logging-in-excel-automation\u002Ftrack-run-history-and-step-timings-of-a-report-job\u002F","Track Run History and Step Timings of a Report Job"," — choosing limits from history.",[1473,1607,1608,1612],{},[14,1609,1611],{"href":1610},"\u002Fautomating-reporting-workflows\u002Ferror-handling-and-logging-in-excel-automation\u002Fsend-an-alert-when-an-excel-report-job-fails\u002F","Send an Alert When an Excel Report Job Fails"," — alerting when a limit fires.",[1614,1615,1616],"style",{},"html pre.shiki code .sMTad, html code.shiki .sMTad{--shiki-default:#6F42C1;--shiki-dark:#FFB757}html pre.shiki code .srMev, html code.shiki .srMev{--shiki-default:#032F62;--shiki-dark:#ADDCFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s-kum, html code.shiki .s-kum{--shiki-default:#D73A49;--shiki-dark:#FF9492}html pre.shiki code .skGVy, html code.shiki .skGVy{--shiki-default:#24292E;--shiki-dark:#F0F3F6}html pre.shiki code .s_Opv, html code.shiki .s_Opv{--shiki-default:#6F42C1;--shiki-dark:#DBB7FF}html pre.shiki code .sP0c6, html code.shiki .sP0c6{--shiki-default:#005CC5;--shiki-dark:#91CBFF}html pre.shiki code .sSjpA, html code.shiki .sSjpA{--shiki-default:#005CC5;--shiki-dark:#FF9492}html pre.shiki code .sa561, html code.shiki .sa561{--shiki-default:#E36209;--shiki-dark:#FFB757}html pre.shiki code .s-wDw, html code.shiki .s-wDw{--shiki-default:#6A737D;--shiki-dark:#BDC4CC}",{"title":30,"searchDepth":82,"depth":82,"links":1618},[1619,1620,1621,1622,1623,1624,1625,1626,1627,1628],{"id":22,"depth":82,"text":23},{"id":61,"depth":82,"text":62},{"id":562,"depth":82,"text":563},{"id":796,"depth":82,"text":797},{"id":1308,"depth":82,"text":1309},{"id":1460,"depth":82,"text":1461},{"id":1467,"depth":82,"text":1468},{"id":1509,"depth":82,"text":1510},{"id":1526,"depth":82,"text":1527},{"id":1582,"depth":82,"text":1583},"2026-10-02","Stop a scheduled Excel report job from hanging forever: give every network call, SMTP connection and subprocess an explicit timeout, run a step that has no timeout option of its own in a child process that can be killed, and put an overall limit on the whole job in the scheduler — with each timeout tested against a deliberately slow server and command.","md",[1633,1635,1637,1639],{"q":1532,"a":1634},"No; without timeout= a request can wait forever. Pass a tuple such as (3.05, 30) for connect and read timeouts.",{"q":1545,"a":1636},"Use subprocess.run(..., timeout=seconds); when the time is up, the child is killed and TimeoutExpired is raised.",{"q":1558,"a":1638},"Run it in a multiprocessing.Process, call join(limit), and terminate the process if it is still alive; threads cannot be stopped from outside.",{"q":1571,"a":1640},"Wrap the command in timeout, for example timeout --kill-after=60s 30m python job.py, or set a time limit in systemd or Task Scheduler.","timeout python excel report job",null,{"breadcrumb":1644},[1645,1648,1651],{"name":1646,"item":1647},"Home","\u002F",{"name":1649,"item":1650},"Automating Reporting Workflows with Python and Excel","\u002Fautomating-reporting-workflows\u002F",{"name":17,"item":16},"\u002Fautomating-reporting-workflows\u002Ferror-handling-and-logging-in-excel-automation\u002Fset-timeouts-on-steps-of-an-excel-report-job",{"title":1654,"description":1655},"Set Timeouts on Steps of a Python Excel Report Job","Timeouts for a report job: requests connect and read timeouts, smtplib timeout, subprocess timeout for LibreOffice, a killable child process for stuck steps, and a job-level limit in cron or systemd.","set-timeouts-on-steps-of-an-excel-report-job","automating-reporting-workflows\u002Ferror-handling-and-logging-in-excel-automation\u002Fset-timeouts-on-steps-of-an-excel-report-job\u002Findex",[1659],"reliable-jobs","how-to","e7ZAxnXbya8beHA0LT2UQIgtp7ExfEng0KwbpNQcvU4",[1663,1666],{"title":1611,"path":1664,"stem":1665,"children":-1},"\u002Fautomating-reporting-workflows\u002Ferror-handling-and-logging-in-excel-automation\u002Fsend-an-alert-when-an-excel-report-job-fails","automating-reporting-workflows\u002Ferror-handling-and-logging-in-excel-automation\u002Fsend-an-alert-when-an-excel-report-job-fails\u002Findex",{"title":1667,"path":1668,"stem":1669,"children":-1},"Skip a Report Run When the Input File Has Not Changed","\u002Fautomating-reporting-workflows\u002Ferror-handling-and-logging-in-excel-automation\u002Fskip-a-report-when-the-input-file-has-not-changed","automating-reporting-workflows\u002Ferror-handling-and-logging-in-excel-automation\u002Fskip-a-report-when-the-input-file-has-not-changed\u002Findex",[1671,1673],{"path":1672,"title":17},"\u002Fautomating-reporting-workflows\u002Ferror-handling-and-logging-in-excel-automation",{"path":1674,"title":1649},"\u002Fautomating-reporting-workflows",[],[1677,1681,1684,1687,1690,1693],{"path":1678,"title":1679,"kind":1680},"\u002Fautomating-reporting-workflows\u002Ftesting-and-packaging-excel-automation-scripts","Testing and Packaging Excel Automation Scripts","guide",{"path":1682,"title":1683,"kind":1660},"\u002Fautomating-reporting-workflows\u002Ftesting-and-packaging-excel-automation-scripts\u002Fblock-large-or-sensitive-excel-files-with-a-pre-commit-hook","Block Large or Sensitive Excel Files with a Pre-commit Hook",{"path":1685,"title":1686,"kind":1660},"\u002Fautomating-reporting-workflows\u002Ftesting-and-packaging-excel-automation-scripts\u002Fbuild-a-command-line-tool-for-excel-reports-with-argparse","Build a Command-Line Tool for Excel Reports with argparse",{"path":1688,"title":1689,"kind":1660},"\u002Fautomating-reporting-workflows\u002Ftesting-and-packaging-excel-automation-scripts\u002Fcompare-excel-output-to-a-golden-file-in-tests","Compare Excel Output to a Golden File in Tests",{"path":1691,"title":1692,"kind":1660},"\u002Fautomating-reporting-workflows\u002Ftesting-and-packaging-excel-automation-scripts\u002Fcreate-excel-test-fixtures-in-memory","Create Excel Test Fixtures in Memory for pytest",{"path":1694,"title":1695,"kind":1660},"\u002Fautomating-reporting-workflows\u002Ftesting-and-packaging-excel-automation-scripts\u002Fdiff-excel-files-in-git-with-a-textconv-driver","Diff Excel Files in Git with a textconv Driver",1790972132193]