{
  "id": "P127",
  "slug": "timeout-budget",
  "key": "P127-timeout-budget",
  "title": "Timeout budget",
  "summary": "A request through four hops, bounded by a fixed timeout for every caller or by the user's deadline passed down: the same wait and answer for the user, but only the fixed timeouts leave work running that nobody waits for.",
  "entry": "main.eml",
  "ui": "terminal",
  "readme": "# P127 - Timeout budget\n\nA request passes through hops A, B, C and D. Each does its own work, then\ncalls the next and waits for its reply. Two ways to bound the wait: a\nfixed timeout for every caller, or the user's deadline passed down.\n\n- `main.eml` - the menu, the hops, one request under both schemes event\n  by event, both side by side, one hop slower and slower, and the\n  settings\n- `hops.eml` - the hops' start times, the fixed scheme, the work done\n  after someone stopped waiting, the deadline scheme, and a summary\n\nHow each part works:\n\n- As in the corpus case `timeout-budget-across-hops`:\n  - fixed: every caller waits at most the timeout for its callee. A hop\n    that gives up cannot stop the hops below it, which keep working;\n  - budget: the user's deadline, the same number, is passed down. No hop\n    starts after it, and work still running at the deadline stops there.\n- Times are milliseconds from the user's call. With A 400, B 600, C 800\n  and D 1000 and a timeout of 5000, both schemes answer in 2800 ms. With\n  D at 8000, both fail at 5000 ms; under fixed timeouts D works on to 9800\n  ms, 4800 ms of work after the user has given up.\n- The user waits the same time and gets the same answer under both\n  schemes, whatever the hops do. The fixed scheme leaves work running\n  after someone above stopped waiting exactly when the request fails; the\n  deadline never does.\n- As in the corpus case `timeout-vs-completion`, a timeout is a fact about\n  the caller: under fixed timeouts the user is told the request failed,\n  and every hop still does all its work.\n\nWhat is checked: menu choices 1 to 7; a hop A, B, C or D; a hop's work\nfrom 0 to 20000 ms; a timeout from 100 to 20000 ms. An empty answer\ncancels.\n\nSessions: `sessions/basic.in` gives:\n- the hops and one request, both schemes answering in 2800 ms;\n- D slower and slower, from 0 to 10000 ms;\n- D at 8000 ms: one request, event by event;\n- a timeout of 3000 ms, side by side.\n\n`sessions/bad-input.in` gives:\n- menu choices 0 and x;\n- hops: a cancel and E for the sweep, a cancel and Z to set;\n- work for A: a cancel, 20001 and x; timeouts: a cancel, 99, 20001 and\n  abc;\n- A at 0 ms with a timeout of 100 ms: A replies \"failed\" to the user just\n  in time, while B, C and D work on.\n\nBuilt on the verified corpus cases `timeout-budget-across-hops` (per-hop\ntimeouts add up, and a deadline passed down stops the waste) and\n`timeout-vs-completion` (the client gives up; the server finishes\nanyway).\n",
  "modules": [
    {
      "name": "main.eml",
      "eml": "# P127 timeout budget: the hops, one request under fixed timeouts and under\n# a passed-down deadline, both side by side, one hop slower and slower, and\n# the settings.\nimport hops\n\ndef plain(s):\n    0 => i\n    len(s) => j\n    while i < j and s[i] == \" \":\n        i + 1 => i\n    while j > i and s[j - 1] == \" \":\n        j - 1 => j\n    return s[i:j]\n\ndef pad(s, n):\n    s => out\n    while len(out) < n:\n        out + \" \" => out\n    return out\n\ndef lpad(s, n):\n    s => out\n    while len(out) < n:\n        \" \" + out => out\n    return out\n\ndef ok_text(ok):\n    if ok:\n        return \"ok\"\n    return \"failed\"\n\ndef show(work, t):\n    \"\" => line\n    0 => total\n    for i in [0:3]:\n        if line != \"\":\n            line + \", \" => line\n        line + hops.names()[i] + \" \" + str(work[i]) + \" ms\" => line\n        total + work[i] => total\n    (\"Each hop's own work: \" + line + \"; \" + str(total) + \" ms in all when nothing waits.\") ^0\n    (\"Timeout: every caller waits up to \" + str(t) + \" ms (fixed), or the user's deadline of \" + str(t) + \" ms is passed down (budget).\") ^0\n\ndef order(ev):\n    # events sorted by time; events at the same time keep their order\n    [] => out\n    for e in ev:\n        [] => nxt\n        False => placed\n        for x in out:\n            if not placed and e[0] < x[0]:\n                nxt + [e] => nxt\n                True => placed\n            nxt + [x] => nxt\n        if not placed:\n            nxt + [e] => nxt\n        nxt => out\n    return out\n\ndef print_events(ev):\n    for e in order(ev):\n        (\"  \" + lpad(str(e[0]), 6) + \" ms  \" + e[1]) ^0\n\ndef timeline_fixed(work, t):\n    hops.fixed(work, t) => r\n    r[0] => hs\n    hops.names() => nm\n    [[0, \"the user calls A, and waits up to \" + str(t) + \" ms\"]] => ev\n    for i in [0:3]:\n        ev + [[hs[i][0], nm[i] + \" starts its work, \" + str(work[i]) + \" ms\"]] => ev\n        if i < 3:\n            ev + [[hs[i][1], nm[i] + \" calls \" + nm[i + 1] + \", and waits up to \" + str(t) + \" ms\"]] => ev\n    ev + [[hs[3][1], \"D finishes its work\"]] => ev\n    for j in [0:2]:\n        2 - j => i\n        if hs[i][4] >= 0:\n            ev + [[hs[i][4], nm[i] + \" gives up on \" + nm[i + 1] + \", and replies: failed\"]] => ev\n            ev + [[hs[i + 1][2], nm[i + 1] + \" replies to \" + nm[i] + \", which has already given up\"]] => ev\n        else:\n            ev + [[hs[i + 1][2], nm[i + 1] + \" replies to \" + nm[i] + \": \" + ok_text(hs[i + 1][3])]] => ev\n    if r[1][2] >= 0:\n        ev + [[t, \"the user gives up: the request failed\"]] => ev\n        ev + [[hs[0][2], \"A replies to the user, who has already given up\"]] => ev\n    else:\n        ev + [[hs[0][2], \"A replies to the user: \" + ok_text(r[1][1])]] => ev\n    \"Fixed timeouts:\" ^0\n    print_events(ev)\n\ndef timeline_budget(work, t):\n    hops.budget(work, t) => r\n    r[0] => hs\n    hops.names() => nm\n    [[0, \"the user calls A, with a deadline at \" + str(t) + \" ms\"]] => ev\n    False => stopped\n    for i in [0:3]:\n        if not stopped:\n            if hs[i][0] < 0:\n                ev + [[t, nm[i] + \" is not started: the deadline has come\"]] => ev\n                True => stopped\n            else:\n                ev + [[hs[i][0], nm[i] + \" starts its work, \" + str(work[i]) + \" ms, with \" + str(t - hs[i][0]) + \" ms left\"]] => ev\n                if not hs[i][2]:\n                    ev + [[t, nm[i] + \" stops at the deadline, \" + str(hs[i][0] + work[i] - t) + \" ms of its work not done\"]] => ev\n                    True => stopped\n                elif i < 3:\n                    ev + [[hs[i][1], nm[i] + \" calls \" + nm[i + 1]]] => ev\n                else:\n                    ev + [[hs[i][1], \"D finishes its work\"]] => ev\n    if r[1][1]:\n        ev + [[r[1][0], \"the user gets the reply: ok\"]] => ev\n    else:\n        ev + [[t, \"the deadline: the request failed, and no work runs past it\"]] => ev\n    \"A deadline passed down:\" ^0\n    print_events(ev)\n\ndef summary_lines(work, t):\n    hops.summary(work, t) => s\n    s[0] => f\n    s[1] => b\n    (\"  fixed:  the user waits \" + str(f[0]) + \" ms, \" + ok_text(f[1]) + \"; \" + str(f[2]) + \" ms of work done after someone above had stopped waiting\") ^0\n    if not f[1]:\n        \"          the user is told it failed, yet every hop does all its work\" ^0\n    (\"  budget: the user waits \" + str(b[0]) + \" ms, \" + ok_text(b[1]) + \"; \" + str(b[2]) + \" of 4 hops started, no work after the deadline\") ^0\n\ndef sweep(work, t):\n    plain(input(\"hop (A, B, C or D)> \")) => h\n    if h == \"\":\n        \"Cancelled.\" ^0\n        return\n    0 - 1 => k\n    for i in [0:3]:\n        if hops.names()[i] == h:\n            i => k\n    if k < 0:\n        \"Type A, B, C or D.\" ^0\n        return\n    (h + \" slower and slower, the others as they are, timeout \" + str(t) + \" ms:\") ^0\n    \"  \" + pad(h + \"'s work\", 10) + \"  fixed: wait  result  wasted   budget: wait  result  hops\" ^0\n    for step in [0:10]:\n        [] => w\n        for i in [0:3]:\n            if i == k:\n                w + [step * 1000] => w\n            else:\n                w + [work[i]] => w\n        hops.summary(w, t) => s\n        (\"  \" + lpad(str(step * 1000), 10) + lpad(str(s[0][0]), 14) + \"  \" + pad(ok_text(s[0][1]), 6) + lpad(str(s[0][2]), 8) + lpad(str(s[1][0]), 15) + \"  \" + pad(ok_text(s[1][1]), 6) + lpad(str(s[1][2]), 6)) ^0\n    \"Both wait the same; only the fixed timeouts leave work running that nobody waits for.\" ^0\n\ndef ask_ms(prompt, low, high):\n    plain(input(prompt)) => t\n    if t == \"\":\n        \"Cancelled.\" ^0\n        return 0 - 1\n    if len(t) > 5:\n        (\"Type a whole number from \" + str(low) + \" to \" + str(high) + \".\") ^0\n        return 0 - 1\n    for c in t:\n        if c < \"0\" or c > \"9\":\n            (\"Type a whole number from \" + str(low) + \" to \" + str(high) + \".\") ^0\n            return 0 - 1\n    if int(t) < low or int(t) > high:\n        (\"Type a whole number from \" + str(low) + \" to \" + str(high) + \".\") ^0\n        return 0 - 1\n    return int(t)\n\ndef set_work(work):\n    plain(input(\"hop (A, B, C or D)> \")) => h\n    if h == \"\":\n        \"Cancelled.\" ^0\n        return work\n    0 - 1 => k\n    for i in [0:3]:\n        if hops.names()[i] == h:\n            i => k\n    if k < 0:\n        \"Type A, B, C or D.\" ^0\n        return work\n    ask_ms(\"work (0 to 20000 ms)> \", 0, 20000) => w\n    if w < 0:\n        return work\n    [] => out\n    for i in [0:3]:\n        if i == k:\n            out + [w] => out\n        else:\n            out + [work[i]] => out\n    (h + \" now works \" + str(w) + \" ms.\") ^0\n    return out\n\n[400, 600, 800, 1000] => work\n5000 => t\n\n\"== Timeout budget ==\" ^0\n\"A request through hops A, B, C and D, each doing its own work and then waiting for the next, bounded by fixed timeouts or by a deadline passed down.\" ^0\nTrue => running\nwhile running:\n    \"\" ^0\n    \"1) hops  2) one request  3) side by side  4) one hop slower  5) set a hop  6) timeout  7) quit\" ^0\n    plain(input(\"choice> \")) => choice\n    if choice == \"1\":\n        show(work, t)\n    elif choice == \"2\":\n        timeline_fixed(work, t)\n        timeline_budget(work, t)\n        summary_lines(work, t)\n    elif choice == \"3\":\n        summary_lines(work, t)\n    elif choice == \"4\":\n        sweep(work, t)\n    elif choice == \"5\":\n        set_work(work) => work\n    elif choice == \"6\":\n        ask_ms(\"timeout (100 to 20000 ms)> \", 100, 20000) => x\n        if x >= 0:\n            x => t\n            (\"Timeout and deadline now \" + str(t) + \" ms.\") ^0\n    elif choice == \"7\":\n        False => running\n    else:\n        \"Pick a number from 1 to 7.\" ^0\n\"Bye.\" ^0\n",
      "python": "import hops\n\ndef plain(s):\n    i = 0\n    j = len(s)\n    while i < j and s[i] == \" \":\n        i = i + 1\n    while j > i and s[j - 1] == \" \":\n        j = j - 1\n    return s[i:j]\n\ndef pad(s, n):\n    out = s\n    while len(out) < n:\n        out = out + \" \"\n    return out\n\ndef lpad(s, n):\n    out = s\n    while len(out) < n:\n        out = \" \" + out\n    return out\n\ndef ok_text(ok):\n    if ok:\n        return \"ok\"\n    return \"failed\"\n\ndef show(work, t):\n    line = \"\"\n    total = 0\n    for i in range(0, 4):\n        if line != \"\":\n            line = line + \", \"\n        line = line + hops.names()[i] + \" \" + str(work[i]) + \" ms\"\n        total = total + work[i]\n    print(\"Each hop's own work: \" + line + \"; \" + str(total) + \" ms in all when nothing waits.\")\n    print(\"Timeout: every caller waits up to \" + str(t) + \" ms (fixed), or the user's deadline of \" + str(t) + \" ms is passed down (budget).\")\n\ndef order(ev):\n    out = []\n    for e in ev:\n        nxt = []\n        placed = False\n        for x in out:\n            if not placed and e[0] < x[0]:\n                nxt = nxt + [e]\n                placed = True\n            nxt = nxt + [x]\n        if not placed:\n            nxt = nxt + [e]\n        out = nxt\n    return out\n\ndef print_events(ev):\n    for e in order(ev):\n        print(\"  \" + lpad(str(e[0]), 6) + \" ms  \" + e[1])\n\ndef timeline_fixed(work, t):\n    r = hops.fixed(work, t)\n    hs = r[0]\n    nm = hops.names()\n    ev = [[0, \"the user calls A, and waits up to \" + str(t) + \" ms\"]]\n    for i in range(0, 4):\n        ev = ev + [[hs[i][0], nm[i] + \" starts its work, \" + str(work[i]) + \" ms\"]]\n        if i < 3:\n            ev = ev + [[hs[i][1], nm[i] + \" calls \" + nm[i + 1] + \", and waits up to \" + str(t) + \" ms\"]]\n    ev = ev + [[hs[3][1], \"D finishes its work\"]]\n    for j in range(0, 3):\n        i = 2 - j\n        if hs[i][4] >= 0:\n            ev = ev + [[hs[i][4], nm[i] + \" gives up on \" + nm[i + 1] + \", and replies: failed\"]]\n            ev = ev + [[hs[i + 1][2], nm[i + 1] + \" replies to \" + nm[i] + \", which has already given up\"]]\n        else:\n            ev = ev + [[hs[i + 1][2], nm[i + 1] + \" replies to \" + nm[i] + \": \" + ok_text(hs[i + 1][3])]]\n    if r[1][2] >= 0:\n        ev = ev + [[t, \"the user gives up: the request failed\"]]\n        ev = ev + [[hs[0][2], \"A replies to the user, who has already given up\"]]\n    else:\n        ev = ev + [[hs[0][2], \"A replies to the user: \" + ok_text(r[1][1])]]\n    print(\"Fixed timeouts:\")\n    print_events(ev)\n\ndef timeline_budget(work, t):\n    r = hops.budget(work, t)\n    hs = r[0]\n    nm = hops.names()\n    ev = [[0, \"the user calls A, with a deadline at \" + str(t) + \" ms\"]]\n    stopped = False\n    for i in range(0, 4):\n        if not stopped:\n            if hs[i][0] < 0:\n                ev = ev + [[t, nm[i] + \" is not started: the deadline has come\"]]\n                stopped = True\n            else:\n                ev = ev + [[hs[i][0], nm[i] + \" starts its work, \" + str(work[i]) + \" ms, with \" + str(t - hs[i][0]) + \" ms left\"]]\n                if not hs[i][2]:\n                    ev = ev + [[t, nm[i] + \" stops at the deadline, \" + str(hs[i][0] + work[i] - t) + \" ms of its work not done\"]]\n                    stopped = True\n                elif i < 3:\n                    ev = ev + [[hs[i][1], nm[i] + \" calls \" + nm[i + 1]]]\n                else:\n                    ev = ev + [[hs[i][1], \"D finishes its work\"]]\n    if r[1][1]:\n        ev = ev + [[r[1][0], \"the user gets the reply: ok\"]]\n    else:\n        ev = ev + [[t, \"the deadline: the request failed, and no work runs past it\"]]\n    print(\"A deadline passed down:\")\n    print_events(ev)\n\ndef summary_lines(work, t):\n    s = hops.summary(work, t)\n    f = s[0]\n    b = s[1]\n    print(\"  fixed:  the user waits \" + str(f[0]) + \" ms, \" + ok_text(f[1]) + \"; \" + str(f[2]) + \" ms of work done after someone above had stopped waiting\")\n    if not f[1]:\n        print(\"          the user is told it failed, yet every hop does all its work\")\n    print(\"  budget: the user waits \" + str(b[0]) + \" ms, \" + ok_text(b[1]) + \"; \" + str(b[2]) + \" of 4 hops started, no work after the deadline\")\n\ndef sweep(work, t):\n    h = plain(input(\"hop (A, B, C or D)> \"))\n    if h == \"\":\n        print(\"Cancelled.\")\n        return\n    k = 0 - 1\n    for i in range(0, 4):\n        if hops.names()[i] == h:\n            k = i\n    if k < 0:\n        print(\"Type A, B, C or D.\")\n        return\n    print(h + \" slower and slower, the others as they are, timeout \" + str(t) + \" ms:\")\n    print(\"  \" + pad(h + \"'s work\", 10) + \"  fixed: wait  result  wasted   budget: wait  result  hops\")\n    for step in range(0, 11):\n        w = []\n        for i in range(0, 4):\n            if i == k:\n                w = w + [step * 1000]\n            else:\n                w = w + [work[i]]\n        s = hops.summary(w, t)\n        print(\"  \" + lpad(str(step * 1000), 10) + lpad(str(s[0][0]), 14) + \"  \" + pad(ok_text(s[0][1]), 6) + lpad(str(s[0][2]), 8) + lpad(str(s[1][0]), 15) + \"  \" + pad(ok_text(s[1][1]), 6) + lpad(str(s[1][2]), 6))\n    print(\"Both wait the same; only the fixed timeouts leave work running that nobody waits for.\")\n\ndef ask_ms(prompt, low, high):\n    t = plain(input(prompt))\n    if t == \"\":\n        print(\"Cancelled.\")\n        return 0 - 1\n    if len(t) > 5:\n        print(\"Type a whole number from \" + str(low) + \" to \" + str(high) + \".\")\n        return 0 - 1\n    for c in t:\n        if c < \"0\" or c > \"9\":\n            print(\"Type a whole number from \" + str(low) + \" to \" + str(high) + \".\")\n            return 0 - 1\n    if int(t) < low or int(t) > high:\n        print(\"Type a whole number from \" + str(low) + \" to \" + str(high) + \".\")\n        return 0 - 1\n    return int(t)\n\ndef set_work(work):\n    h = plain(input(\"hop (A, B, C or D)> \"))\n    if h == \"\":\n        print(\"Cancelled.\")\n        return work\n    k = 0 - 1\n    for i in range(0, 4):\n        if hops.names()[i] == h:\n            k = i\n    if k < 0:\n        print(\"Type A, B, C or D.\")\n        return work\n    w = ask_ms(\"work (0 to 20000 ms)> \", 0, 20000)\n    if w < 0:\n        return work\n    out = []\n    for i in range(0, 4):\n        if i == k:\n            out = out + [w]\n        else:\n            out = out + [work[i]]\n    print(h + \" now works \" + str(w) + \" ms.\")\n    return out\n\nwork = [400, 600, 800, 1000]\nt = 5000\nprint(\"== Timeout budget ==\")\nprint(\"A request through hops A, B, C and D, each doing its own work and then waiting for the next, bounded by fixed timeouts or by a deadline passed down.\")\nrunning = True\nwhile running:\n    print(\"\")\n    print(\"1) hops  2) one request  3) side by side  4) one hop slower  5) set a hop  6) timeout  7) quit\")\n    choice = plain(input(\"choice> \"))\n    if choice == \"1\":\n        show(work, t)\n    elif choice == \"2\":\n        timeline_fixed(work, t)\n        timeline_budget(work, t)\n        summary_lines(work, t)\n    elif choice == \"3\":\n        summary_lines(work, t)\n    elif choice == \"4\":\n        sweep(work, t)\n    elif choice == \"5\":\n        work = set_work(work)\n    elif choice == \"6\":\n        x = ask_ms(\"timeout (100 to 20000 ms)> \", 100, 20000)\n        if x >= 0:\n            t = x\n            print(\"Timeout and deadline now \" + str(t) + \" ms.\")\n    elif choice == \"7\":\n        running = False\n    else:\n        print(\"Pick a number from 1 to 7.\")\nprint(\"Bye.\")\n"
    },
    {
      "name": "hops.eml",
      "eml": "# P127 timeout budget - a request passes through hops A, B, C and D: each\n# does its own work, then calls the next and waits for its reply. As in the\n# corpus case timeout-budget-across-hops, two ways to bound the wait:\n#   fixed   every caller waits at most the timeout for its callee; a hop\n#           that gives up cannot stop the hops below it, which keep working;\n#   budget  the user's deadline, the same timeout, is passed down; no hop\n#           starts after it, and work still running at the deadline stops.\n# As in timeout-vs-completion, a timeout is a fact about the caller: under\n# fixed timeouts the user can be told the request failed while every hop\n# finishes its work anyway.\n#\n# Times are milliseconds from the user's call.\n\ndef names():\n    return [\"A\", \"B\", \"C\", \"D\"]\n\ndef starts(work):\n    [] => out\n    0 => s\n    for w in work:\n        out + [s] => out\n        s + w => s\n    return out\n\ndef fixed(work, t):\n    # per hop [start, end of its work, reply time, ok, gave up at or -1],\n    # and the user's [wait, ok, gave up at or -1]\n    len(work) => n\n    starts(work) => st\n    [] => hops\n    for i in [0:n - 1]:\n        hops + [[st[i], st[i] + work[i], st[i] + work[i], True, 0 - 1]] => hops\n    for j in [0:n - 2]:\n        n - 2 - j => i\n        hops[i][1] => call\n        if hops[i + 1][2] - call <= t:\n            hops[i + 1][2] => hops[i][2]\n            hops[i + 1][3] => hops[i][3]\n        else:\n            call + t => hops[i][2]\n            False => hops[i][3]\n            call + t => hops[i][4]\n    if hops[0][2] <= t:\n        return [hops, [hops[0][2], hops[0][3], 0 - 1]]\n    return [hops, [t, False, t]]\n\ndef wasted(work, t):\n    # milliseconds of work done after someone above that hop stopped waiting\n    fixed(work, t) => r\n    r[0] => hops\n    r[1][2] => cut\n    0 => total\n    for i in [0:len(work) - 1]:\n        if cut >= 0 and hops[i][1] > cut:\n            hops[i][1] - max2(hops[i][0], cut) => more\n            total + more => total\n        if hops[i][4] >= 0 and (cut < 0 or hops[i][4] < cut):\n            hops[i][4] => cut\n    return total\n\ndef max2(x, y):\n    if x > y:\n        return x\n    return y\n\ndef budget(work, d):\n    # per hop [start or -1 if never started, when its work ended or stopped,\n    #  finished]; the user's [wait, ok]\n    starts(work) => st\n    [] => hops\n    0 => total\n    for i in [0:len(work) - 1]:\n        total + work[i] => total\n        if st[i] >= d:\n            hops + [[0 - 1, 0 - 1, False]] => hops\n        elif st[i] + work[i] > d:\n            hops + [[st[i], d, False]] => hops\n        else:\n            hops + [[st[i], st[i] + work[i], True]] => hops\n    if total <= d:\n        return [hops, [total, True]]\n    return [hops, [d, False]]\n\ndef summary(work, t):\n    # [fixed wait, ok, wasted, every hop's work done], [budget wait, ok,\n    #  hops started, every hop's work done]\n    fixed(work, t)[1] => f\n    budget(work, t) => b\n    0 => started\n    True => done\n    for h in b[0]:\n        if h[0] >= 0:\n            started + 1 => started\n        if not h[2]:\n            False => done\n    return [[f[0], f[1], wasted(work, t), True], [b[1][0], b[1][1], started, done]]\n",
      "python": "def names():\n    return [\"A\", \"B\", \"C\", \"D\"]\n\ndef starts(work):\n    out = []\n    s = 0\n    for w in work:\n        out = out + [s]\n        s = s + w\n    return out\n\ndef fixed(work, t):\n    n = len(work)\n    st = starts(work)\n    hops = []\n    for i in range(0, n):\n        hops = hops + [[st[i], st[i] + work[i], st[i] + work[i], True, 0 - 1]]\n    for j in range(0, n - 2+1):\n        i = n - 2 - j\n        call = hops[i][1]\n        if hops[i + 1][2] - call <= t:\n            hops[i][2] = hops[i + 1][2]\n            hops[i][3] = hops[i + 1][3]\n        else:\n            hops[i][2] = call + t\n            hops[i][3] = False\n            hops[i][4] = call + t\n    if hops[0][2] <= t:\n        return [hops, [hops[0][2], hops[0][3], 0 - 1]]\n    return [hops, [t, False, t]]\n\ndef wasted(work, t):\n    r = fixed(work, t)\n    hops = r[0]\n    cut = r[1][2]\n    total = 0\n    for i in range(0, len(work)):\n        if cut >= 0 and hops[i][1] > cut:\n            more = hops[i][1] - max2(hops[i][0], cut)\n            total = total + more\n        if hops[i][4] >= 0 and (cut < 0 or hops[i][4] < cut):\n            cut = hops[i][4]\n    return total\n\ndef max2(x, y):\n    if x > y:\n        return x\n    return y\n\ndef budget(work, d):\n    st = starts(work)\n    hops = []\n    total = 0\n    for i in range(0, len(work)):\n        total = total + work[i]\n        if st[i] >= d:\n            hops = hops + [[0 - 1, 0 - 1, False]]\n        elif st[i] + work[i] > d:\n            hops = hops + [[st[i], d, False]]\n        else:\n            hops = hops + [[st[i], st[i] + work[i], True]]\n    if total <= d:\n        return [hops, [total, True]]\n    return [hops, [d, False]]\n\ndef summary(work, t):\n    f = fixed(work, t)[1]\n    b = budget(work, t)\n    started = 0\n    done = True\n    for h in b[0]:\n        if h[0] >= 0:\n            started = started + 1\n        if not h[2]:\n            done = False\n    return [[f[0], f[1], wasted(work, t), True], [b[1][0], b[1][1], started, done]]\n"
    }
  ],
  "sessions": [
    {
      "name": "bad-input",
      "input": "0\nx\n4\n\n4\nE\n5\n\n5\nZ\n5\nA\n\n5\nA\n20001\n5\nA\nx\n6\n\n6\n99\n6\n20001\n6\nabc\n5\nA\n0\n6\n100\n2\n7\n",
      "screen": "== Timeout budget ==\nA request through hops A, B, C and D, each doing its own work and then waiting for the next, bounded by fixed timeouts or by a deadline passed down.\n\n1) hops  2) one request  3) side by side  4) one hop slower  5) set a hop  6) timeout  7) quit\nchoice> 0\nPick a number from 1 to 7.\n\n1) hops  2) one request  3) side by side  4) one hop slower  5) set a hop  6) timeout  7) quit\nchoice> x\nPick a number from 1 to 7.\n\n1) hops  2) one request  3) side by side  4) one hop slower  5) set a hop  6) timeout  7) quit\nchoice> 4\nhop (A, B, C or D)> \nCancelled.\n\n1) hops  2) one request  3) side by side  4) one hop slower  5) set a hop  6) timeout  7) quit\nchoice> 4\nhop (A, B, C or D)> E\nType A, B, C or D.\n\n1) hops  2) one request  3) side by side  4) one hop slower  5) set a hop  6) timeout  7) quit\nchoice> 5\nhop (A, B, C or D)> \nCancelled.\n\n1) hops  2) one request  3) side by side  4) one hop slower  5) set a hop  6) timeout  7) quit\nchoice> 5\nhop (A, B, C or D)> Z\nType A, B, C or D.\n\n1) hops  2) one request  3) side by side  4) one hop slower  5) set a hop  6) timeout  7) quit\nchoice> 5\nhop (A, B, C or D)> A\nwork (0 to 20000 ms)> \nCancelled.\n\n1) hops  2) one request  3) side by side  4) one hop slower  5) set a hop  6) timeout  7) quit\nchoice> 5\nhop (A, B, C or D)> A\nwork (0 to 20000 ms)> 20001\nType a whole number from 0 to 20000.\n\n1) hops  2) one request  3) side by side  4) one hop slower  5) set a hop  6) timeout  7) quit\nchoice> 5\nhop (A, B, C or D)> A\nwork (0 to 20000 ms)> x\nType a whole number from 0 to 20000.\n\n1) hops  2) one request  3) side by side  4) one hop slower  5) set a hop  6) timeout  7) quit\nchoice> 6\ntimeout (100 to 20000 ms)> \nCancelled.\n\n1) hops  2) one request  3) side by side  4) one hop slower  5) set a hop  6) timeout  7) quit\nchoice> 6\ntimeout (100 to 20000 ms)> 99\nType a whole number from 100 to 20000.\n\n1) hops  2) one request  3) side by side  4) one hop slower  5) set a hop  6) timeout  7) quit\nchoice> 6\ntimeout (100 to 20000 ms)> 20001\nType a whole number from 100 to 20000.\n\n1) hops  2) one request  3) side by side  4) one hop slower  5) set a hop  6) timeout  7) quit\nchoice> 6\ntimeout (100 to 20000 ms)> abc\nType a whole number from 100 to 20000.\n\n1) hops  2) one request  3) side by side  4) one hop slower  5) set a hop  6) timeout  7) quit\nchoice> 5\nhop (A, B, C or D)> A\nwork (0 to 20000 ms)> 0\nA now works 0 ms.\n\n1) hops  2) one request  3) side by side  4) one hop slower  5) set a hop  6) timeout  7) quit\nchoice> 6\ntimeout (100 to 20000 ms)> 100\nTimeout and deadline now 100 ms.\n\n1) hops  2) one request  3) side by side  4) one hop slower  5) set a hop  6) timeout  7) quit\nchoice> 2\nFixed timeouts:\n       0 ms  the user calls A, and waits up to 100 ms\n       0 ms  A starts its work, 0 ms\n       0 ms  A calls B, and waits up to 100 ms\n       0 ms  B starts its work, 600 ms\n     100 ms  A gives up on B, and replies: failed\n     100 ms  A replies to the user: failed\n     600 ms  B calls C, and waits up to 100 ms\n     600 ms  C starts its work, 800 ms\n     700 ms  B gives up on C, and replies: failed\n     700 ms  B replies to A, which has already given up\n    1400 ms  C calls D, and waits up to 100 ms\n    1400 ms  D starts its work, 1000 ms\n    1500 ms  C gives up on D, and replies: failed\n    1500 ms  C replies to B, which has already given up\n    2400 ms  D finishes its work\n    2400 ms  D replies to C, which has already given up\nA deadline passed down:\n       0 ms  the user calls A, with a deadline at 100 ms\n       0 ms  A starts its work, 0 ms, with 100 ms left\n       0 ms  A calls B\n       0 ms  B starts its work, 600 ms, with 100 ms left\n     100 ms  B stops at the deadline, 500 ms of its work not done\n     100 ms  the deadline: the request failed, and no work runs past it\n  fixed:  the user waits 100 ms, failed; 2300 ms of work done after someone above had stopped waiting\n          the user is told it failed, yet every hop does all its work\n  budget: the user waits 100 ms, failed; 2 of 4 hops started, no work after the deadline\n\n1) hops  2) one request  3) side by side  4) one hop slower  5) set a hop  6) timeout  7) quit\nchoice> 7\nBye.\n",
      "interpreter": "equal"
    },
    {
      "name": "basic",
      "input": "1\n2\n4\nD\n5\nD\n8000\n2\n6\n3000\n3\n7\n",
      "screen": "== Timeout budget ==\nA request through hops A, B, C and D, each doing its own work and then waiting for the next, bounded by fixed timeouts or by a deadline passed down.\n\n1) hops  2) one request  3) side by side  4) one hop slower  5) set a hop  6) timeout  7) quit\nchoice> 1\nEach hop's own work: A 400 ms, B 600 ms, C 800 ms, D 1000 ms; 2800 ms in all when nothing waits.\nTimeout: every caller waits up to 5000 ms (fixed), or the user's deadline of 5000 ms is passed down (budget).\n\n1) hops  2) one request  3) side by side  4) one hop slower  5) set a hop  6) timeout  7) quit\nchoice> 2\nFixed timeouts:\n       0 ms  the user calls A, and waits up to 5000 ms\n       0 ms  A starts its work, 400 ms\n     400 ms  A calls B, and waits up to 5000 ms\n     400 ms  B starts its work, 600 ms\n    1000 ms  B calls C, and waits up to 5000 ms\n    1000 ms  C starts its work, 800 ms\n    1800 ms  C calls D, and waits up to 5000 ms\n    1800 ms  D starts its work, 1000 ms\n    2800 ms  D finishes its work\n    2800 ms  D replies to C: ok\n    2800 ms  C replies to B: ok\n    2800 ms  B replies to A: ok\n    2800 ms  A replies to the user: ok\nA deadline passed down:\n       0 ms  the user calls A, with a deadline at 5000 ms\n       0 ms  A starts its work, 400 ms, with 5000 ms left\n     400 ms  A calls B\n     400 ms  B starts its work, 600 ms, with 4600 ms left\n    1000 ms  B calls C\n    1000 ms  C starts its work, 800 ms, with 4000 ms left\n    1800 ms  C calls D\n    1800 ms  D starts its work, 1000 ms, with 3200 ms left\n    2800 ms  D finishes its work\n    2800 ms  the user gets the reply: ok\n  fixed:  the user waits 2800 ms, ok; 0 ms of work done after someone above had stopped waiting\n  budget: the user waits 2800 ms, ok; 4 of 4 hops started, no work after the deadline\n\n1) hops  2) one request  3) side by side  4) one hop slower  5) set a hop  6) timeout  7) quit\nchoice> 4\nhop (A, B, C or D)> D\nD slower and slower, the others as they are, timeout 5000 ms:\n  D's work    fixed: wait  result  wasted   budget: wait  result  hops\n           0          1800  ok           0           1800  ok         4\n        1000          2800  ok           0           2800  ok         4\n        2000          3800  ok           0           3800  ok         4\n        3000          4800  ok           0           4800  ok         4\n        4000          5000  failed     800           5000  failed     4\n        5000          5000  failed    1800           5000  failed     4\n        6000          5000  failed    2800           5000  failed     4\n        7000          5000  failed    3800           5000  failed     4\n        8000          5000  failed    4800           5000  failed     4\n        9000          5000  failed    5800           5000  failed     4\n       10000          5000  failed    6800           5000  failed     4\nBoth wait the same; only the fixed timeouts leave work running that nobody waits for.\n\n1) hops  2) one request  3) side by side  4) one hop slower  5) set a hop  6) timeout  7) quit\nchoice> 5\nhop (A, B, C or D)> D\nwork (0 to 20000 ms)> 8000\nD now works 8000 ms.\n\n1) hops  2) one request  3) side by side  4) one hop slower  5) set a hop  6) timeout  7) quit\nchoice> 2\nFixed timeouts:\n       0 ms  the user calls A, and waits up to 5000 ms\n       0 ms  A starts its work, 400 ms\n     400 ms  A calls B, and waits up to 5000 ms\n     400 ms  B starts its work, 600 ms\n    1000 ms  B calls C, and waits up to 5000 ms\n    1000 ms  C starts its work, 800 ms\n    1800 ms  C calls D, and waits up to 5000 ms\n    1800 ms  D starts its work, 8000 ms\n    5000 ms  the user gives up: the request failed\n    5400 ms  A gives up on B, and replies: failed\n    5400 ms  A replies to the user, who has already given up\n    6000 ms  B gives up on C, and replies: failed\n    6000 ms  B replies to A, which has already given up\n    6800 ms  C gives up on D, and replies: failed\n    6800 ms  C replies to B, which has already given up\n    9800 ms  D finishes its work\n    9800 ms  D replies to C, which has already given up\nA deadline passed down:\n       0 ms  the user calls A, with a deadline at 5000 ms\n       0 ms  A starts its work, 400 ms, with 5000 ms left\n     400 ms  A calls B\n     400 ms  B starts its work, 600 ms, with 4600 ms left\n    1000 ms  B calls C\n    1000 ms  C starts its work, 800 ms, with 4000 ms left\n    1800 ms  C calls D\n    1800 ms  D starts its work, 8000 ms, with 3200 ms left\n    5000 ms  D stops at the deadline, 4800 ms of its work not done\n    5000 ms  the deadline: the request failed, and no work runs past it\n  fixed:  the user waits 5000 ms, failed; 4800 ms of work done after someone above had stopped waiting\n          the user is told it failed, yet every hop does all its work\n  budget: the user waits 5000 ms, failed; 4 of 4 hops started, no work after the deadline\n\n1) hops  2) one request  3) side by side  4) one hop slower  5) set a hop  6) timeout  7) quit\nchoice> 6\ntimeout (100 to 20000 ms)> 3000\nTimeout and deadline now 3000 ms.\n\n1) hops  2) one request  3) side by side  4) one hop slower  5) set a hop  6) timeout  7) quit\nchoice> 3\n  fixed:  the user waits 3000 ms, failed; 6800 ms of work done after someone above had stopped waiting\n          the user is told it failed, yet every hop does all its work\n  budget: the user waits 3000 ms, failed; 4 of 4 hops started, no work after the deadline\n\n1) hops  2) one request  3) side by side  4) one hop slower  5) set a hop  6) timeout  7) quit\nchoice> 7\nBye.\n",
      "interpreter": "equal"
    }
  ],
  "builtOn": [
    {
      "slug": "timeout-budget-across-hops",
      "caseId": "258-timeout-budget-across-hops",
      "title": "Timeout budget across hops — four 5-second timeouts are not a 5-second bound"
    },
    {
      "slug": "timeout-vs-completion",
      "caseId": "228-timeout-vs-completion",
      "title": "A timeout is a fact about the client"
    }
  ],
  "updated": "2026-10-17"
}
