{
  "id": "P160",
  "slug": "upload-limits",
  "key": "P160-upload-limits",
  "title": "Upload limits",
  "summary": "One upload endpoint limited by files, by bytes and by both: 100 files lets in anywhere from under a megabyte to hundreds, and a byte limit bounds the most any upload gets in.",
  "entry": "main.eml",
  "ui": "terminal",
  "readme": "# P160 - Upload limits\n\nOne upload endpoint limited three ways - by files, by bytes and by both -\nand the most each lets in.\n\n- `main.eml` - the menu, the uploads, the uploads under each limit, and an\n  upload of your own\n- `limits.eml` - the two limits, the uploads and the sizes of their files,\n  and what a limit lets in\n\nHow each part works:\n\n- Four uploads: 150 thumbnails of about 8 kB, 60 receipts of about 120 kB,\n  100 photos of about 3.5 MB and 40 scans of about 9 MB.\n- As in the corpus case `cost-model-mismatch`, a limit of 100 files is\n  written in the unit that is easy to count, and what runs out is memory\n  and bytes on the wire. Under it an upload gets anywhere from 0.9 MB to\n  432.0 MB in.\n- A limit is judged by the most it lets in, not by making uploads alike: at\n  20.0 MB the byte limit lets the small uploads through whole and stops\n  every upload at 20.0 MB, 19.6 MB at most here. Both limits together never\n  let in more than either alone.\n\nWhat is checked: menu choices 1 to 4; for an upload of your own, 1 to 999\nfiles of about 1 to 99999 kB each. An empty answer cancels.\n\nSessions: `sessions/basic.in` gives:\n- the uploads and the limits;\n- 300 files of about 9000 kB, and 20 files of about 5 kB.\n\n`sessions/bad-input.in` gives:\n- menu choices 0 and x;\n- files: a cancel, 0, 1000 and x; then 10 files with a cancel, 0, 100000\n  and x for the size;\n- 150 files of about 8 kB, where the file limit lets in less than the byte\n  limit.\n\nBuilt on the verified corpus case `cost-model-mismatch` (the same workloads\nthrough a count limit, a size limit and both, judged by the maximum each\nadmits).\n",
  "modules": [
    {
      "name": "main.eml",
      "eml": "# P160 upload limits: the uploads, each one under a limit on files, on\n# bytes and on both, and an upload of your own.\nimport limits\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 mb(kb):\n    # kilobytes as megabytes to one place, rounded half up\n    int((kb + 50) / 100) => t\n    t % 10 => d\n    return str(int((t - d) / 10)) + \".\" + str(d) + \" MB\"\n\ndef total(sizes):\n    0 => t\n    for s in sizes:\n        t + s => t\n    return t\n\ndef show_uploads():\n    \"The uploads, as their files come in:\" ^0\n    for u in limits.uploads():\n        limits.files(u) => fs\n        (\"  \" + pad(u[0], 12) + lpad(str(len(fs)), 4) + \" files of about \" + lpad(str(u[2]), 4) + \" kB, \" + lpad(mb(total(fs)), 9) + \" in all\") ^0\n    (\"The limits: at most \" + str(limits.count_limit()) + \" files; at most \" + mb(limits.byte_limit()) + \"; or both.\") ^0\n\ndef cell(r):\n    return str(r[0]) + \" files, \" + mb(r[1])\n\ndef compare(us):\n    (\"  \" + pad(\"upload\", 12) + pad(\"100 files\", 22) + pad(\"20.0 MB\", 22) + \"both\") ^0\n    [0, 0, 0] => most\n    [0, 0, 0] => least\n    0 => first\n    for u in us:\n        limits.files(u) => fs\n        [limits.admit(fs, \"count\"), limits.admit(fs, \"bytes\"), limits.admit(fs, \"both\")] => rs\n        (\"  \" + pad(u[0], 12) + pad(cell(rs[0]), 22) + pad(cell(rs[1]), 22) + cell(rs[2])) ^0\n        0 => k\n        while k < 3:\n            if rs[k][1] > most[k]:\n                rs[k][1] => most[k]\n            if first == 0 or rs[k][1] < least[k]:\n                rs[k][1] => least[k]\n            k + 1 => k\n        1 => first\n    if len(us) == 1:\n        (\"The file limit lets in \" + mb(most[0]) + \" of it, the byte limit \" + mb(most[1]) + \", both \" + mb(most[2]) + \".\") ^0\n        if most[0] > limits.byte_limit():\n            \"Counted in files, the limit does not hold back the bytes that run out.\" ^0\n    else:\n        (\"The most let in: \" + mb(most[0]) + \" under the file limit, \" + mb(most[1]) + \" under the byte limit, \" + mb(most[2]) + \" under both.\") ^0\n        if most[0] > limits.byte_limit():\n            (\"Under the 100-file limit an upload gets anywhere from \" + mb(least[0]) + \" to \" + mb(most[0]) + \" in: the limit counts what is easy to count, not what runs out.\") ^0\n        \"The byte limit lets small uploads through whole and stops every upload at 20.0 MB: a limit bounds the most, it does not make uploads alike.\" ^0\n\ndef own():\n    plain(input(\"how many files (1 to 999)> \")) => t\n    if t == \"\":\n        \"Cancelled.\" ^0\n        return\n    if len(t) > 3 or not digits(t) or int(t) < 1:\n        \"Type a whole number from 1 to 999.\" ^0\n        return\n    plain(input(\"about how many kB each (1 to 99999)> \")) => k\n    if k == \"\":\n        \"Cancelled.\" ^0\n        return\n    if len(k) > 5 or not digits(k) or int(k) < 1:\n        \"Type a whole number from 1 to 99999.\" ^0\n        return\n    [\"yours\", int(t), int(k)] => u\n    compare([u])\n\ndef digits(t):\n    for c in t:\n        if c < \"0\" or c > \"9\":\n            return False\n    return len(t) > 0\n\n\"== Upload limits ==\" ^0\n\"One upload endpoint limited by files, by bytes and by both, and what each lets in.\" ^0\nTrue => running\nwhile running:\n    \"\" ^0\n    \"1) the uploads  2) the limits  3) an upload of your own  4) quit\" ^0\n    plain(input(\"choice> \")) => choice\n    if choice == \"1\":\n        show_uploads()\n    elif choice == \"2\":\n        compare(limits.uploads())\n    elif choice == \"3\":\n        own()\n    elif choice == \"4\":\n        False => running\n    else:\n        \"Pick a number from 1 to 4.\" ^0\n\"Bye.\" ^0\n",
      "python": "import limits\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 mb(kb):\n    t = int((kb + 50) / 100)\n    d = t % 10\n    return str(int((t - d) / 10)) + \".\" + str(d) + \" MB\"\n\ndef total(sizes):\n    t = 0\n    for s in sizes:\n        t = t + s\n    return t\n\ndef show_uploads():\n    print(\"The uploads, as their files come in:\")\n    for u in limits.uploads():\n        fs = limits.files(u)\n        print(\"  \" + pad(u[0], 12) + lpad(str(len(fs)), 4) + \" files of about \" + lpad(str(u[2]), 4) + \" kB, \" + lpad(mb(total(fs)), 9) + \" in all\")\n    print(\"The limits: at most \" + str(limits.count_limit()) + \" files; at most \" + mb(limits.byte_limit()) + \"; or both.\")\n\ndef cell(r):\n    return str(r[0]) + \" files, \" + mb(r[1])\n\ndef compare(us):\n    print(\"  \" + pad(\"upload\", 12) + pad(\"100 files\", 22) + pad(\"20.0 MB\", 22) + \"both\")\n    most = [0, 0, 0]\n    least = [0, 0, 0]\n    first = 0\n    for u in us:\n        fs = limits.files(u)\n        rs = [limits.admit(fs, \"count\"), limits.admit(fs, \"bytes\"), limits.admit(fs, \"both\")]\n        print(\"  \" + pad(u[0], 12) + pad(cell(rs[0]), 22) + pad(cell(rs[1]), 22) + cell(rs[2]))\n        k = 0\n        while k < 3:\n            if rs[k][1] > most[k]:\n                most[k] = rs[k][1]\n            if first == 0 or rs[k][1] < least[k]:\n                least[k] = rs[k][1]\n            k = k + 1\n        first = 1\n    if len(us) == 1:\n        print(\"The file limit lets in \" + mb(most[0]) + \" of it, the byte limit \" + mb(most[1]) + \", both \" + mb(most[2]) + \".\")\n        if most[0] > limits.byte_limit():\n            print(\"Counted in files, the limit does not hold back the bytes that run out.\")\n    else:\n        print(\"The most let in: \" + mb(most[0]) + \" under the file limit, \" + mb(most[1]) + \" under the byte limit, \" + mb(most[2]) + \" under both.\")\n        if most[0] > limits.byte_limit():\n            print(\"Under the 100-file limit an upload gets anywhere from \" + mb(least[0]) + \" to \" + mb(most[0]) + \" in: the limit counts what is easy to count, not what runs out.\")\n        print(\"The byte limit lets small uploads through whole and stops every upload at 20.0 MB: a limit bounds the most, it does not make uploads alike.\")\n\ndef own():\n    t = plain(input(\"how many files (1 to 999)> \"))\n    if t == \"\":\n        print(\"Cancelled.\")\n        return\n    if len(t) > 3 or not digits(t) or int(t) < 1:\n        print(\"Type a whole number from 1 to 999.\")\n        return\n    k = plain(input(\"about how many kB each (1 to 99999)> \"))\n    if k == \"\":\n        print(\"Cancelled.\")\n        return\n    if len(k) > 5 or not digits(k) or int(k) < 1:\n        print(\"Type a whole number from 1 to 99999.\")\n        return\n    u = [\"yours\", int(t), int(k)]\n    compare([u])\n\ndef digits(t):\n    for c in t:\n        if c < \"0\" or c > \"9\":\n            return False\n    return len(t) > 0\n\nprint(\"== Upload limits ==\")\nprint(\"One upload endpoint limited by files, by bytes and by both, and what each lets in.\")\nrunning = True\nwhile running:\n    print(\"\")\n    print(\"1) the uploads  2) the limits  3) an upload of your own  4) quit\")\n    choice = plain(input(\"choice> \"))\n    if choice == \"1\":\n        show_uploads()\n    elif choice == \"2\":\n        compare(limits.uploads())\n    elif choice == \"3\":\n        own()\n    elif choice == \"4\":\n        running = False\n    else:\n        print(\"Pick a number from 1 to 4.\")\nprint(\"Bye.\")\n"
    },
    {
      "name": "limits.eml",
      "eml": "# P160 upload limits - one upload endpoint, limited three ways. As in the\n# corpus case cost-model-mismatch, a limit of 100 files is written in the\n# unit that is easy to count, and what runs out is memory and bytes on the\n# wire: 100 files is anywhere from a few kilobytes to many megabytes,\n# depending on who is uploading. A limit's job is to bound the most it lets\n# in, not to make every upload the same size, so each limit is judged by the\n# largest upload it admits. Sizes are in kilobytes.\n\ndef count_limit():\n    return 100\n\ndef byte_limit():\n    # 20 megabytes, in kilobytes\n    return 20000\n\ndef uploads():\n    # [name, the sizes of its files in kilobytes, in the order they come]\n    return [[\"thumbnails\", 150, 8], [\"receipts\", 60, 120], [\"photos\", 100, 3500], [\"scans\", 40, 9000]]\n\ndef files(u):\n    # the sizes of upload u's files: u[1] of them, sizes around u[2]\n    [] => out\n    0 => i\n    while i < u[1]:\n        out + [u[2] + int((i % 5) * u[2] / 10)] => out\n        i + 1 => i\n    return out\n\ndef admit(sizes, mode):\n    # [files let in, kilobytes let in, files refused]\n    0 => n\n    0 => kb\n    0 => refused\n    for s in sizes:\n        True => ok\n        if mode == \"count\" or mode == \"both\":\n            if n >= count_limit():\n                False => ok\n        if mode == \"bytes\" or mode == \"both\":\n            if kb + s > byte_limit():\n                False => ok\n        if ok:\n            n + 1 => n\n            kb + s => kb\n        else:\n            refused + 1 => refused\n    return [n, kb, refused]\n",
      "python": "def count_limit():\n    return 100\n\ndef byte_limit():\n    return 20000\n\ndef uploads():\n    return [[\"thumbnails\", 150, 8], [\"receipts\", 60, 120], [\"photos\", 100, 3500], [\"scans\", 40, 9000]]\n\ndef files(u):\n    out = []\n    i = 0\n    while i < u[1]:\n        out = out + [u[2] + int(i % 5 * u[2] / 10)]\n        i = i + 1\n    return out\n\ndef admit(sizes, mode):\n    n = 0\n    kb = 0\n    refused = 0\n    for s in sizes:\n        ok = True\n        if mode == \"count\" or mode == \"both\":\n            if n >= count_limit():\n                ok = False\n        if mode == \"bytes\" or mode == \"both\":\n            if kb + s > byte_limit():\n                ok = False\n        if ok:\n            n = n + 1\n            kb = kb + s\n        else:\n            refused = refused + 1\n    return [n, kb, refused]\n"
    }
  ],
  "sessions": [
    {
      "name": "bad-input",
      "input": "0\nx\n3\n\n3\n0\n3\n1000\n3\nx\n3\n10\n\n3\n10\n0\n3\n10\n100000\n3\n10\nx\n3\n150\n8\n4\n",
      "screen": "== Upload limits ==\nOne upload endpoint limited by files, by bytes and by both, and what each lets in.\n\n1) the uploads  2) the limits  3) an upload of your own  4) quit\nchoice> 0\nPick a number from 1 to 4.\n\n1) the uploads  2) the limits  3) an upload of your own  4) quit\nchoice> x\nPick a number from 1 to 4.\n\n1) the uploads  2) the limits  3) an upload of your own  4) quit\nchoice> 3\nhow many files (1 to 999)> \nCancelled.\n\n1) the uploads  2) the limits  3) an upload of your own  4) quit\nchoice> 3\nhow many files (1 to 999)> 0\nType a whole number from 1 to 999.\n\n1) the uploads  2) the limits  3) an upload of your own  4) quit\nchoice> 3\nhow many files (1 to 999)> 1000\nType a whole number from 1 to 999.\n\n1) the uploads  2) the limits  3) an upload of your own  4) quit\nchoice> 3\nhow many files (1 to 999)> x\nType a whole number from 1 to 999.\n\n1) the uploads  2) the limits  3) an upload of your own  4) quit\nchoice> 3\nhow many files (1 to 999)> 10\nabout how many kB each (1 to 99999)> \nCancelled.\n\n1) the uploads  2) the limits  3) an upload of your own  4) quit\nchoice> 3\nhow many files (1 to 999)> 10\nabout how many kB each (1 to 99999)> 0\nType a whole number from 1 to 99999.\n\n1) the uploads  2) the limits  3) an upload of your own  4) quit\nchoice> 3\nhow many files (1 to 999)> 10\nabout how many kB each (1 to 99999)> 100000\nType a whole number from 1 to 99999.\n\n1) the uploads  2) the limits  3) an upload of your own  4) quit\nchoice> 3\nhow many files (1 to 999)> 10\nabout how many kB each (1 to 99999)> x\nType a whole number from 1 to 99999.\n\n1) the uploads  2) the limits  3) an upload of your own  4) quit\nchoice> 3\nhow many files (1 to 999)> 150\nabout how many kB each (1 to 99999)> 8\n  upload      100 files             20.0 MB               both\n  yours       100 files, 0.9 MB     150 files, 1.4 MB     100 files, 0.9 MB\nThe file limit lets in 0.9 MB of it, the byte limit 1.4 MB, both 0.9 MB.\n\n1) the uploads  2) the limits  3) an upload of your own  4) quit\nchoice> 4\nBye.\n",
      "interpreter": "equal"
    },
    {
      "name": "basic",
      "input": "1\n2\n3\n300\n9000\n3\n20\n5\n4\n",
      "screen": "== Upload limits ==\nOne upload endpoint limited by files, by bytes and by both, and what each lets in.\n\n1) the uploads  2) the limits  3) an upload of your own  4) quit\nchoice> 1\nThe uploads, as their files come in:\n  thumbnails   150 files of about    8 kB,    1.4 MB in all\n  receipts      60 files of about  120 kB,    8.6 MB in all\n  photos       100 files of about 3500 kB,  420.0 MB in all\n  scans         40 files of about 9000 kB,  432.0 MB in all\nThe limits: at most 100 files; at most 20.0 MB; or both.\n\n1) the uploads  2) the limits  3) an upload of your own  4) quit\nchoice> 2\n  upload      100 files             20.0 MB               both\n  thumbnails  100 files, 0.9 MB     150 files, 1.4 MB     100 files, 0.9 MB\n  receipts    60 files, 8.6 MB      60 files, 8.6 MB      60 files, 8.6 MB\n  photos      100 files, 420.0 MB   5 files, 19.6 MB      5 files, 19.6 MB\n  scans       40 files, 432.0 MB    2 files, 18.9 MB      2 files, 18.9 MB\nThe most let in: 432.0 MB under the file limit, 19.6 MB under the byte limit, 19.6 MB under both.\nUnder the 100-file limit an upload gets anywhere from 0.9 MB to 432.0 MB in: the limit counts what is easy to count, not what runs out.\nThe byte limit lets small uploads through whole and stops every upload at 20.0 MB: a limit bounds the most, it does not make uploads alike.\n\n1) the uploads  2) the limits  3) an upload of your own  4) quit\nchoice> 3\nhow many files (1 to 999)> 300\nabout how many kB each (1 to 99999)> 9000\n  upload      100 files             20.0 MB               both\n  yours       100 files, 1080.0 MB  2 files, 18.9 MB      2 files, 18.9 MB\nThe file limit lets in 1080.0 MB of it, the byte limit 18.9 MB, both 18.9 MB.\nCounted in files, the limit does not hold back the bytes that run out.\n\n1) the uploads  2) the limits  3) an upload of your own  4) quit\nchoice> 3\nhow many files (1 to 999)> 20\nabout how many kB each (1 to 99999)> 5\n  upload      100 files             20.0 MB               both\n  yours       20 files, 0.1 MB      20 files, 0.1 MB      20 files, 0.1 MB\nThe file limit lets in 0.1 MB of it, the byte limit 0.1 MB, both 0.1 MB.\n\n1) the uploads  2) the limits  3) an upload of your own  4) quit\nchoice> 4\nBye.\n",
      "interpreter": "equal"
    }
  ],
  "builtOn": [
    {
      "slug": "cost-model-mismatch",
      "caseId": "277-cost-model-mismatch",
      "title": "Cost model mismatch — a limit of 100 items, and the resource is bytes"
    }
  ],
  "updated": "2026-10-18"
}
