{
  "id": "P116",
  "slug": "deadlock-detector",
  "key": "P116-deadlock-detector",
  "title": "Deadlock detector",
  "summary": "Threads taking and releasing locks by a schedule, turn by turn, with the wait-for cycle that is a deadlock; every interleaving counted, the serial ones that never deadlock, and one lock order that removes it.",
  "entry": "main.eml",
  "ui": "terminal",
  "readme": "# P116 - Deadlock detector\n\nThreads take and release locks in the order a schedule gives them turns.\nA thread whose next lock is held waits; when every thread in a cycle waits\nfor a lock the next one holds, none of them can ever move.\n\n- `main.eml` - the menu, the threads, one schedule turn by turn, and every\n  interleaving counted\n- `locks.eml` - the four sets of threads, a turn, the wait-for cycle, a run\n  of a schedule, and the count over every interleaving\n\nHow each part works:\n\n- A thread is a list of steps: lock A, lock B, unlock B, unlock A. On its\n  turn it takes its next step. A lock held by another thread makes it\n  wait, and it tries the same step again on its next turn.\n- The wait-for graph has an arrow from each waiting thread to the thread\n  that holds its lock. A cycle in it is a deadlock: each thread in it\n  waits for the next, so no turn of theirs can move. Two threads can\n  already be stuck before either waits: after 1 2, T1 holds A and T2 holds\n  B, and the program runs the rest to show it ends in deadlock.\n- As in the corpus case `deadlock-by-lock-order`, every interleaving of\n  the two opposite threads is run, the rest run out at the end:\n  - 40 of the 70 deadlock, exactly those that start 1 2 or 2 1;\n  - the 2 serial ones, one thread finishing before the other starts, never\n    do, and they are all that a test without forced overlap sees.\n- Taking locks in one order, A before B before C, removes the deadlock on\n  every interleaving. Three threads in a ring deadlock on 14,400 of 34,650\n  interleavings; with T3 taking A before C, on none. Those two counts were\n  run in the cross-check; the program states them.\n\nWhat is checked: menu choices 1 to 5; a schedule of thread numbers from 1\nto the number of threads, separated by spaces, up to 30 turns; one of the\nfour sets of threads. An empty answer cancels.\n\nSessions: `sessions/basic.in` gives:\n- the opposite threads one after the other, then 1 2 1 2 (a deadlock),\n  then 1 2 (no one waits yet, and the rest deadlock), and the count;\n- the ordered threads with 1 2 1 2 1 2 1 2 (T2 waits, and both finish)\n  and the count;\n- the ring with 1 2 3 1 2 3 (a deadlock of three) and its count;\n- the ordered ring with turns 1 2 3 four times, and its count.\n\n`sessions/bad-input.in` gives:\n- menu choices 0 and x;\n- schedules: a cancel, 1 3, 1 a, 1 12 and 31 turns;\n- other threads: a cancel and circle;\n- the schedule 2 2 2 2 2, a turn for T2 after it has finished.\n\nBuilt on the verified corpus case `deadlock-by-lock-order` (two threads\ntaking two locks in opposite orders, and one global order that removes the\ndeadlock).\n",
  "modules": [
    {
      "name": "main.eml",
      "eml": "# P116 deadlock detector: threads taking locks, one schedule step by step\n# with its wait-for graph, every interleaving counted, and one global lock\n# order that removes the deadlock.\nimport locks\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 program_text(p):\n    \"\" => out\n    for s in p:\n        if out != \"\":\n            out + \", \" => out\n        out + s[0] + \" \" + s[1] => out\n    return out\n\ndef show(name, progs):\n    (\"Threads (\" + name + \"):\") ^0\n    for t in [0:len(progs) - 1]:\n        (\"  T\" + str(t + 1) + \": \" + program_text(progs[t])) ^0\n\ndef run_one(progs):\n    (\"A schedule: whose turn it is, such as 1 2 1 2 (threads 1 to \" + str(len(progs)) + \", up to 30 turns).\") ^0\n    plain(input(\"schedule> \")) => t\n    if t == \"\":\n        \"Cancelled.\" ^0\n        return\n    [] => sched\n    \"\" => w\n    for c in t + \" \":\n        if c == \" \":\n            if w != \"\":\n                if len(w) != 1 or w[0] < \"1\" or w[0] > str(len(progs)):\n                    (\"Type thread numbers from 1 to \" + str(len(progs)) + \", separated by spaces.\") ^0\n                    return\n                sched + [int(w) - 1] => sched\n            \"\" => w\n        else:\n            w + c => w\n    if len(sched) == 0 or len(sched) > 30:\n        \"Up to 30 turns.\" ^0\n        return\n    locks.run(progs, sched, False) => r\n    for e in r[0]:\n        \"T\" + str(e[0] + 1) => who\n        if e[2] == \"done\":\n            (\"  \" + who + \" has finished\") ^0\n        else:\n            progs[e[0]][e[1]] => s\n            if e[2] == \"take\":\n                (\"  \" + who + \" takes \" + s[1]) ^0\n            elif e[2] == \"release\":\n                (\"  \" + who + \" releases \" + s[1]) ^0\n            else:\n                (\"  \" + who + \" waits for \" + s[1] + \", held by T\" + str(e[3] + 1)) ^0\n    r[1] => st\n    locks.cycle(progs, st) => c\n    if len(c) > 0:\n        \"\" => line\n        for i in [0:len(c) - 1]:\n            c[i] => x\n            c[(i + 1) % len(c)] => y\n            if line != \"\":\n                line + \"; \" => line\n            line + \"T\" + str(x + 1) + \" waits for \" + st[2][x] + \" held by T\" + str(y + 1) => line\n        (\"Deadlock: \" + line + \". No turn of theirs can ever move.\") ^0\n        return\n    0 => left\n    for i in [0:len(progs) - 1]:\n        if st[0][i] < len(progs[i]):\n            left + 1 => left\n    if left == 0:\n        \"Every thread finished and every lock is free.\" ^0\n        return\n    locks.run(progs, sched, True) => after\n    if after[2]:\n        (\"No deadlock yet; \" + str(left) + \" thread\" + plural(left) + \" still to finish, and running the rest, lowest thread first, ends in deadlock.\") ^0\n    else:\n        (\"No deadlock; \" + str(left) + \" thread\" + plural(left) + \" still to finish, and running the rest, lowest thread first, finishes every thread.\") ^0\n\ndef plural(n):\n    if n == 1:\n        return \"\"\n    return \"s\"\n\ndef census(name, progs):\n    if len(progs) > 2:\n        \"Three threads of four steps have 34,650 interleavings, too many to run here. Every one was run in the cross-check:\" ^0\n        if name == \"ring\":\n            \"  14,400 of them end in deadlock; the 6 serial ones do not.\" ^0\n        else:\n            \"  none of them ends in deadlock: every thread takes its locks in the order A, B, C.\" ^0\n        return\n    locks.census(progs) => c\n    (\"Every interleaving of the \" + name + \" threads, each blocked thread trying again on its next turn and the rest run out at the end:\") ^0\n    (\"  \" + str(c[0]) + \" interleavings, \" + str(c[1]) + \" end in deadlock, \" + str(c[0] - c[1]) + \" with every lock free.\") ^0\n    (\"  Serial ones, one thread finishing before the other starts: \" + str(c[2]) + \", of which \" + str(c[3]) + \" deadlock.\") ^0\n    if c[1] > 0:\n        \"A test that does not force the threads to overlap sees only the serial schedules, and they never deadlock.\" ^0\n\n\"opposite\" => name\nlocks.presets(name) => progs\n\n\"== Deadlock detector ==\" ^0\n\"Threads taking and releasing locks by a schedule; a thread waits while its next lock is held, and a cycle of waiting is a deadlock.\" ^0\nTrue => running\nwhile running:\n    \"\" ^0\n    \"1) threads  2) run a schedule  3) every interleaving  4) other threads  5) quit\" ^0\n    plain(input(\"choice> \")) => choice\n    if choice == \"1\":\n        show(name, progs)\n    elif choice == \"2\":\n        run_one(progs)\n    elif choice == \"3\":\n        census(name, progs)\n    elif choice == \"4\":\n        plain(input(\"threads (opposite, ordered, ring or ring-ordered)> \")) => n\n        if n == \"\":\n            \"Cancelled.\" ^0\n        elif len(locks.presets(n)) == 0:\n            \"Type opposite, ordered, ring or ring-ordered.\" ^0\n        else:\n            n => name\n            locks.presets(n) => progs\n            show(name, progs)\n    elif choice == \"5\":\n        False => running\n    else:\n        \"Pick a number from 1 to 5.\" ^0\n\"Bye.\" ^0\n",
      "python": "import locks\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 program_text(p):\n    out = \"\"\n    for s in p:\n        if out != \"\":\n            out = out + \", \"\n        out = out + s[0] + \" \" + s[1]\n    return out\n\ndef show(name, progs):\n    print(\"Threads (\" + name + \"):\")\n    for t in range(0, len(progs)):\n        print(\"  T\" + str(t + 1) + \": \" + program_text(progs[t]))\n\ndef run_one(progs):\n    print(\"A schedule: whose turn it is, such as 1 2 1 2 (threads 1 to \" + str(len(progs)) + \", up to 30 turns).\")\n    t = plain(input(\"schedule> \"))\n    if t == \"\":\n        print(\"Cancelled.\")\n        return\n    sched = []\n    w = \"\"\n    for c in t + \" \":\n        if c == \" \":\n            if w != \"\":\n                if len(w) != 1 or w[0] < \"1\" or w[0] > str(len(progs)):\n                    print(\"Type thread numbers from 1 to \" + str(len(progs)) + \", separated by spaces.\")\n                    return\n                sched = sched + [int(w) - 1]\n            w = \"\"\n        else:\n            w = w + c\n    if len(sched) == 0 or len(sched) > 30:\n        print(\"Up to 30 turns.\")\n        return\n    r = locks.run(progs, sched, False)\n    for e in r[0]:\n        who = \"T\" + str(e[0] + 1)\n        if e[2] == \"done\":\n            print(\"  \" + who + \" has finished\")\n        else:\n            s = progs[e[0]][e[1]]\n            if e[2] == \"take\":\n                print(\"  \" + who + \" takes \" + s[1])\n            elif e[2] == \"release\":\n                print(\"  \" + who + \" releases \" + s[1])\n            else:\n                print(\"  \" + who + \" waits for \" + s[1] + \", held by T\" + str(e[3] + 1))\n    st = r[1]\n    c = locks.cycle(progs, st)\n    if len(c) > 0:\n        line = \"\"\n        for i in range(0, len(c)):\n            x = c[i]\n            y = c[(i + 1) % len(c)]\n            if line != \"\":\n                line = line + \"; \"\n            line = line + \"T\" + str(x + 1) + \" waits for \" + st[2][x] + \" held by T\" + str(y + 1)\n        print(\"Deadlock: \" + line + \". No turn of theirs can ever move.\")\n        return\n    left = 0\n    for i in range(0, len(progs)):\n        if st[0][i] < len(progs[i]):\n            left = left + 1\n    if left == 0:\n        print(\"Every thread finished and every lock is free.\")\n        return\n    after = locks.run(progs, sched, True)\n    if after[2]:\n        print(\"No deadlock yet; \" + str(left) + \" thread\" + plural(left) + \" still to finish, and running the rest, lowest thread first, ends in deadlock.\")\n    else:\n        print(\"No deadlock; \" + str(left) + \" thread\" + plural(left) + \" still to finish, and running the rest, lowest thread first, finishes every thread.\")\n\ndef plural(n):\n    if n == 1:\n        return \"\"\n    return \"s\"\n\ndef census(name, progs):\n    if len(progs) > 2:\n        print(\"Three threads of four steps have 34,650 interleavings, too many to run here. Every one was run in the cross-check:\")\n        if name == \"ring\":\n            print(\"  14,400 of them end in deadlock; the 6 serial ones do not.\")\n        else:\n            print(\"  none of them ends in deadlock: every thread takes its locks in the order A, B, C.\")\n        return\n    c = locks.census(progs)\n    print(\"Every interleaving of the \" + name + \" threads, each blocked thread trying again on its next turn and the rest run out at the end:\")\n    print(\"  \" + str(c[0]) + \" interleavings, \" + str(c[1]) + \" end in deadlock, \" + str(c[0] - c[1]) + \" with every lock free.\")\n    print(\"  Serial ones, one thread finishing before the other starts: \" + str(c[2]) + \", of which \" + str(c[3]) + \" deadlock.\")\n    if c[1] > 0:\n        print(\"A test that does not force the threads to overlap sees only the serial schedules, and they never deadlock.\")\n\nname = \"opposite\"\nprogs = locks.presets(name)\nprint(\"== Deadlock detector ==\")\nprint(\"Threads taking and releasing locks by a schedule; a thread waits while its next lock is held, and a cycle of waiting is a deadlock.\")\nrunning = True\nwhile running:\n    print(\"\")\n    print(\"1) threads  2) run a schedule  3) every interleaving  4) other threads  5) quit\")\n    choice = plain(input(\"choice> \"))\n    if choice == \"1\":\n        show(name, progs)\n    elif choice == \"2\":\n        run_one(progs)\n    elif choice == \"3\":\n        census(name, progs)\n    elif choice == \"4\":\n        n = plain(input(\"threads (opposite, ordered, ring or ring-ordered)> \"))\n        if n == \"\":\n            print(\"Cancelled.\")\n        elif len(locks.presets(n)) == 0:\n            print(\"Type opposite, ordered, ring or ring-ordered.\")\n        else:\n            name = n\n            progs = locks.presets(n)\n            show(name, progs)\n    elif choice == \"5\":\n        running = False\n    else:\n        print(\"Pick a number from 1 to 5.\")\nprint(\"Bye.\")\n"
    },
    {
      "name": "locks.eml",
      "eml": "# P116 deadlock detector - threads that take and release locks, run by a\n# schedule that says whose turn it is. A thread whose next lock is held\n# waits, and tries again on its next turn. A deadlock is a cycle in the\n# wait-for graph: each thread waiting for a lock held by the next. As in\n# the corpus case deadlock-by-lock-order, two threads that take the same\n# two locks in opposite orders deadlock on most interleavings, and taking\n# every lock in one global order removes it.\n#\n# A program is a list of steps [action, lock]: action \"lock\" or \"unlock\".\n\ndef presets(name):\n    if name == \"opposite\":\n        return [[[\"lock\", \"A\"], [\"lock\", \"B\"], [\"unlock\", \"B\"], [\"unlock\", \"A\"]], [[\"lock\", \"B\"], [\"lock\", \"A\"], [\"unlock\", \"A\"], [\"unlock\", \"B\"]]]\n    if name == \"ordered\":\n        return [[[\"lock\", \"A\"], [\"lock\", \"B\"], [\"unlock\", \"B\"], [\"unlock\", \"A\"]], [[\"lock\", \"A\"], [\"lock\", \"B\"], [\"unlock\", \"B\"], [\"unlock\", \"A\"]]]\n    if name == \"ring\":\n        return [[[\"lock\", \"A\"], [\"lock\", \"B\"], [\"unlock\", \"B\"], [\"unlock\", \"A\"]], [[\"lock\", \"B\"], [\"lock\", \"C\"], [\"unlock\", \"C\"], [\"unlock\", \"B\"]], [[\"lock\", \"C\"], [\"lock\", \"A\"], [\"unlock\", \"A\"], [\"unlock\", \"C\"]]]\n    if name == \"ring-ordered\":\n        return [[[\"lock\", \"A\"], [\"lock\", \"B\"], [\"unlock\", \"B\"], [\"unlock\", \"A\"]], [[\"lock\", \"B\"], [\"lock\", \"C\"], [\"unlock\", \"C\"], [\"unlock\", \"B\"]], [[\"lock\", \"A\"], [\"lock\", \"C\"], [\"unlock\", \"C\"], [\"unlock\", \"A\"]]]\n    return []\n\ndef new_state(progs):\n    # [pc per thread, holder per lock, lock waited for per thread (\"\" if none)]\n    [] => pc\n    [] => waiting\n    for p in progs:\n        pc + [0] => pc\n        waiting + [\"\"] => waiting\n    return [pc, {}, waiting]\n\ndef step(progs, st, t):\n    # thread t takes its turn: [what happened, thread that holds it or -1]\n    st[0] => pc\n    st[1] => holder\n    if pc[t] >= len(progs[t]):\n        return [\"done\", 0 - 1]\n    progs[t][pc[t]] => s\n    if s[0] == \"lock\":\n        if s[1] in holder and holder[s[1]] != t:\n            s[1] => st[2][t]\n            return [\"wait\", holder[s[1]]]\n        t => holder[s[1]]\n        \"\" => st[2][t]\n        pc[t] + 1 => pc[t]\n        return [\"take\", 0 - 1]\n    {} => rest\n    for k in holder:\n        if k != s[1]:\n            holder[k] => rest[k]\n    rest => st[1]\n    pc[t] + 1 => pc[t]\n    return [\"release\", 0 - 1]\n\ndef can_go(progs, st, t):\n    if st[0][t] >= len(progs[t]):\n        return False\n    progs[t][st[0][t]] => s\n    if s[0] == \"unlock\":\n        return True\n    return not (s[1] in st[1]) or st[1][s[1]] == t\n\ndef cycle(progs, st):\n    # a cycle in the wait-for graph, as a list of threads, or []\n    [] => edges\n    for t in [0:len(progs) - 1]:\n        0 - 1 => to\n        if st[2][t] != \"\" and st[0][t] < len(progs[t]) and st[2][t] in st[1]:\n            st[1][st[2][t]] => to\n        edges + [to] => edges\n    for s in [0:len(progs) - 1]:\n        [s] => path\n        edges[s] => x\n        while x != 0 - 1 and len(path) <= len(progs):\n            if x == s:\n                return path\n            path + [x] => path\n            edges[x] => x\n    return []\n\ndef run(progs, schedule, drain):\n    # [log, state, deadlocked]: each turn of the schedule, then - with\n    # drain - every thread that can still go, lowest number first, until\n    # none can\n    new_state(progs) => st\n    [] => log\n    for t in schedule:\n        st[0][t] => at\n        step(progs, st, t) => r\n        log + [[t, at, r[0], r[1]]] => log\n    if drain:\n        True => moving\n        while moving:\n            False => moving\n            for t in [0:len(progs) - 1]:\n                if not moving and can_go(progs, st, t):\n                    step(progs, st, t)\n                    True => moving\n    False => stuck\n    for t in [0:len(progs) - 1]:\n        if st[0][t] < len(progs[t]):\n            True => stuck\n    return [log, st, stuck]\n\ndef interleavings(lengths):\n    # every order of the threads' turns, thread t appearing lengths[t] times\n    [[[], lengths]] => frontier\n    [] => out\n    while len(frontier) > 0:\n        frontier[len(frontier) - 1] => item\n        frontier[0:len(frontier) - 1] => frontier\n        item[0] => seq\n        item[1] => left\n        0 => total\n        for n in left:\n            total + n => total\n        if total == 0:\n            out + [seq] => out\n        else:\n            for t in [0:len(left) - 1]:\n                if left[t] > 0:\n                    [] => nl\n                    for i in [0:len(left) - 1]:\n                        if i == t:\n                            nl + [left[i] - 1] => nl\n                        else:\n                            nl + [left[i]] => nl\n                    frontier + [[seq + [t], nl]] => frontier\n    return out\n\ndef census(progs):\n    # over every interleaving: [interleavings, deadlocked, serial ones,\n    # serial ones deadlocked]\n    [] => lengths\n    for p in progs:\n        lengths + [len(p)] => lengths\n    0 => n\n    0 => dead\n    0 => serial\n    0 => serial_dead\n    for seq in interleavings(lengths):\n        n + 1 => n\n        run(progs, seq, True)[2] => stuck\n        if stuck:\n            dead + 1 => dead\n        0 => changes\n        for i in [1:len(seq) - 1]:\n            if seq[i] != seq[i - 1]:\n                changes + 1 => changes\n        if changes == len(progs) - 1:\n            serial + 1 => serial\n            if stuck:\n                serial_dead + 1 => serial_dead\n    return [n, dead, serial, serial_dead]\n",
      "python": "def presets(name):\n    if name == \"opposite\":\n        return [[[\"lock\", \"A\"], [\"lock\", \"B\"], [\"unlock\", \"B\"], [\"unlock\", \"A\"]], [[\"lock\", \"B\"], [\"lock\", \"A\"], [\"unlock\", \"A\"], [\"unlock\", \"B\"]]]\n    if name == \"ordered\":\n        return [[[\"lock\", \"A\"], [\"lock\", \"B\"], [\"unlock\", \"B\"], [\"unlock\", \"A\"]], [[\"lock\", \"A\"], [\"lock\", \"B\"], [\"unlock\", \"B\"], [\"unlock\", \"A\"]]]\n    if name == \"ring\":\n        return [[[\"lock\", \"A\"], [\"lock\", \"B\"], [\"unlock\", \"B\"], [\"unlock\", \"A\"]], [[\"lock\", \"B\"], [\"lock\", \"C\"], [\"unlock\", \"C\"], [\"unlock\", \"B\"]], [[\"lock\", \"C\"], [\"lock\", \"A\"], [\"unlock\", \"A\"], [\"unlock\", \"C\"]]]\n    if name == \"ring-ordered\":\n        return [[[\"lock\", \"A\"], [\"lock\", \"B\"], [\"unlock\", \"B\"], [\"unlock\", \"A\"]], [[\"lock\", \"B\"], [\"lock\", \"C\"], [\"unlock\", \"C\"], [\"unlock\", \"B\"]], [[\"lock\", \"A\"], [\"lock\", \"C\"], [\"unlock\", \"C\"], [\"unlock\", \"A\"]]]\n    return []\n\ndef new_state(progs):\n    pc = []\n    waiting = []\n    for p in progs:\n        pc = pc + [0]\n        waiting = waiting + [\"\"]\n    return [pc, {}, waiting]\n\ndef step(progs, st, t):\n    pc = st[0]\n    holder = st[1]\n    if pc[t] >= len(progs[t]):\n        return [\"done\", 0 - 1]\n    s = progs[t][pc[t]]\n    if s[0] == \"lock\":\n        if s[1] in holder and holder[s[1]] != t:\n            st[2][t] = s[1]\n            return [\"wait\", holder[s[1]]]\n        holder[s[1]] = t\n        st[2][t] = \"\"\n        pc[t] = pc[t] + 1\n        return [\"take\", 0 - 1]\n    rest = {}\n    for k in holder:\n        if k != s[1]:\n            rest[k] = holder[k]\n    st[1] = rest\n    pc[t] = pc[t] + 1\n    return [\"release\", 0 - 1]\n\ndef can_go(progs, st, t):\n    if st[0][t] >= len(progs[t]):\n        return False\n    s = progs[t][st[0][t]]\n    if s[0] == \"unlock\":\n        return True\n    return not s[1] in st[1] or st[1][s[1]] == t\n\ndef cycle(progs, st):\n    edges = []\n    for t in range(0, len(progs)):\n        to = 0 - 1\n        if st[2][t] != \"\" and st[0][t] < len(progs[t]) and st[2][t] in st[1]:\n            to = st[1][st[2][t]]\n        edges = edges + [to]\n    for s in range(0, len(progs)):\n        path = [s]\n        x = edges[s]\n        while x != 0 - 1 and len(path) <= len(progs):\n            if x == s:\n                return path\n            path = path + [x]\n            x = edges[x]\n    return []\n\ndef run(progs, schedule, drain):\n    st = new_state(progs)\n    log = []\n    for t in schedule:\n        at = st[0][t]\n        r = step(progs, st, t)\n        log = log + [[t, at, r[0], r[1]]]\n    if drain:\n        moving = True\n        while moving:\n            moving = False\n            for t in range(0, len(progs)):\n                if not moving and can_go(progs, st, t):\n                    step(progs, st, t)\n                    moving = True\n    stuck = False\n    for t in range(0, len(progs)):\n        if st[0][t] < len(progs[t]):\n            stuck = True\n    return [log, st, stuck]\n\ndef interleavings(lengths):\n    frontier = [[[], lengths]]\n    out = []\n    while len(frontier) > 0:\n        item = frontier[len(frontier) - 1]\n        frontier = frontier[0:len(frontier) - 1]\n        seq = item[0]\n        left = item[1]\n        total = 0\n        for n in left:\n            total = total + n\n        if total == 0:\n            out = out + [seq]\n        else:\n            for t in range(0, len(left)):\n                if left[t] > 0:\n                    nl = []\n                    for i in range(0, len(left)):\n                        if i == t:\n                            nl = nl + [left[i] - 1]\n                        else:\n                            nl = nl + [left[i]]\n                    frontier = frontier + [[seq + [t], nl]]\n    return out\n\ndef census(progs):\n    lengths = []\n    for p in progs:\n        lengths = lengths + [len(p)]\n    n = 0\n    dead = 0\n    serial = 0\n    serial_dead = 0\n    for seq in interleavings(lengths):\n        n = n + 1\n        stuck = run(progs, seq, True)[2]\n        if stuck:\n            dead = dead + 1\n        changes = 0\n        for i in range(1, len(seq)):\n            if seq[i] != seq[i - 1]:\n                changes = changes + 1\n        if changes == len(progs) - 1:\n            serial = serial + 1\n            if stuck:\n                serial_dead = serial_dead + 1\n    return [n, dead, serial, serial_dead]\n"
    }
  ],
  "sessions": [
    {
      "name": "bad-input",
      "input": "0\nx\n2\n\n2\n1 3\n2\n1 a\n2\n1 12\n2\n1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1\n4\n\n4\ncircle\n2\n2 2 2 2 2\n5\n",
      "screen": "== Deadlock detector ==\nThreads taking and releasing locks by a schedule; a thread waits while its next lock is held, and a cycle of waiting is a deadlock.\n\n1) threads  2) run a schedule  3) every interleaving  4) other threads  5) quit\nchoice> 0\nPick a number from 1 to 5.\n\n1) threads  2) run a schedule  3) every interleaving  4) other threads  5) quit\nchoice> x\nPick a number from 1 to 5.\n\n1) threads  2) run a schedule  3) every interleaving  4) other threads  5) quit\nchoice> 2\nA schedule: whose turn it is, such as 1 2 1 2 (threads 1 to 2, up to 30 turns).\nschedule> \nCancelled.\n\n1) threads  2) run a schedule  3) every interleaving  4) other threads  5) quit\nchoice> 2\nA schedule: whose turn it is, such as 1 2 1 2 (threads 1 to 2, up to 30 turns).\nschedule> 1 3\nType thread numbers from 1 to 2, separated by spaces.\n\n1) threads  2) run a schedule  3) every interleaving  4) other threads  5) quit\nchoice> 2\nA schedule: whose turn it is, such as 1 2 1 2 (threads 1 to 2, up to 30 turns).\nschedule> 1 a\nType thread numbers from 1 to 2, separated by spaces.\n\n1) threads  2) run a schedule  3) every interleaving  4) other threads  5) quit\nchoice> 2\nA schedule: whose turn it is, such as 1 2 1 2 (threads 1 to 2, up to 30 turns).\nschedule> 1 12\nType thread numbers from 1 to 2, separated by spaces.\n\n1) threads  2) run a schedule  3) every interleaving  4) other threads  5) quit\nchoice> 2\nA schedule: whose turn it is, such as 1 2 1 2 (threads 1 to 2, up to 30 turns).\nschedule> 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1\nUp to 30 turns.\n\n1) threads  2) run a schedule  3) every interleaving  4) other threads  5) quit\nchoice> 4\nthreads (opposite, ordered, ring or ring-ordered)> \nCancelled.\n\n1) threads  2) run a schedule  3) every interleaving  4) other threads  5) quit\nchoice> 4\nthreads (opposite, ordered, ring or ring-ordered)> circle\nType opposite, ordered, ring or ring-ordered.\n\n1) threads  2) run a schedule  3) every interleaving  4) other threads  5) quit\nchoice> 2\nA schedule: whose turn it is, such as 1 2 1 2 (threads 1 to 2, up to 30 turns).\nschedule> 2 2 2 2 2\n  T2 takes B\n  T2 takes A\n  T2 releases A\n  T2 releases B\n  T2 has finished\nNo deadlock; 1 thread still to finish, and running the rest, lowest thread first, finishes every thread.\n\n1) threads  2) run a schedule  3) every interleaving  4) other threads  5) quit\nchoice> 5\nBye.\n",
      "interpreter": "equal"
    },
    {
      "name": "basic",
      "input": "1\n2\n1 1 1 1 2 2 2 2\n2\n1 2 1 2\n2\n1 2\n3\n4\nordered\n2\n1 2 1 2 1 2 1 2\n3\n4\nring\n2\n1 2 3 1 2 3\n3\n4\nring-ordered\n2\n1 2 3 1 2 3 1 2 3 1 2 3\n3\n5\n",
      "screen": "== Deadlock detector ==\nThreads taking and releasing locks by a schedule; a thread waits while its next lock is held, and a cycle of waiting is a deadlock.\n\n1) threads  2) run a schedule  3) every interleaving  4) other threads  5) quit\nchoice> 1\nThreads (opposite):\n  T1: lock A, lock B, unlock B, unlock A\n  T2: lock B, lock A, unlock A, unlock B\n\n1) threads  2) run a schedule  3) every interleaving  4) other threads  5) quit\nchoice> 2\nA schedule: whose turn it is, such as 1 2 1 2 (threads 1 to 2, up to 30 turns).\nschedule> 1 1 1 1 2 2 2 2\n  T1 takes A\n  T1 takes B\n  T1 releases B\n  T1 releases A\n  T2 takes B\n  T2 takes A\n  T2 releases A\n  T2 releases B\nEvery thread finished and every lock is free.\n\n1) threads  2) run a schedule  3) every interleaving  4) other threads  5) quit\nchoice> 2\nA schedule: whose turn it is, such as 1 2 1 2 (threads 1 to 2, up to 30 turns).\nschedule> 1 2 1 2\n  T1 takes A\n  T2 takes B\n  T1 waits for B, held by T2\n  T2 waits for A, held by T1\nDeadlock: T1 waits for B held by T2; T2 waits for A held by T1. No turn of theirs can ever move.\n\n1) threads  2) run a schedule  3) every interleaving  4) other threads  5) quit\nchoice> 2\nA schedule: whose turn it is, such as 1 2 1 2 (threads 1 to 2, up to 30 turns).\nschedule> 1 2\n  T1 takes A\n  T2 takes B\nNo deadlock yet; 2 threads still to finish, and running the rest, lowest thread first, ends in deadlock.\n\n1) threads  2) run a schedule  3) every interleaving  4) other threads  5) quit\nchoice> 3\nEvery interleaving of the opposite threads, each blocked thread trying again on its next turn and the rest run out at the end:\n  70 interleavings, 40 end in deadlock, 30 with every lock free.\n  Serial ones, one thread finishing before the other starts: 2, of which 0 deadlock.\nA test that does not force the threads to overlap sees only the serial schedules, and they never deadlock.\n\n1) threads  2) run a schedule  3) every interleaving  4) other threads  5) quit\nchoice> 4\nthreads (opposite, ordered, ring or ring-ordered)> ordered\nThreads (ordered):\n  T1: lock A, lock B, unlock B, unlock A\n  T2: lock A, lock B, unlock B, unlock A\n\n1) threads  2) run a schedule  3) every interleaving  4) other threads  5) quit\nchoice> 2\nA schedule: whose turn it is, such as 1 2 1 2 (threads 1 to 2, up to 30 turns).\nschedule> 1 2 1 2 1 2 1 2\n  T1 takes A\n  T2 waits for A, held by T1\n  T1 takes B\n  T2 waits for A, held by T1\n  T1 releases B\n  T2 waits for A, held by T1\n  T1 releases A\n  T2 takes A\nNo deadlock; 1 thread still to finish, and running the rest, lowest thread first, finishes every thread.\n\n1) threads  2) run a schedule  3) every interleaving  4) other threads  5) quit\nchoice> 3\nEvery interleaving of the ordered threads, each blocked thread trying again on its next turn and the rest run out at the end:\n  70 interleavings, 0 end in deadlock, 70 with every lock free.\n  Serial ones, one thread finishing before the other starts: 2, of which 0 deadlock.\n\n1) threads  2) run a schedule  3) every interleaving  4) other threads  5) quit\nchoice> 4\nthreads (opposite, ordered, ring or ring-ordered)> ring\nThreads (ring):\n  T1: lock A, lock B, unlock B, unlock A\n  T2: lock B, lock C, unlock C, unlock B\n  T3: lock C, lock A, unlock A, unlock C\n\n1) threads  2) run a schedule  3) every interleaving  4) other threads  5) quit\nchoice> 2\nA schedule: whose turn it is, such as 1 2 1 2 (threads 1 to 3, up to 30 turns).\nschedule> 1 2 3 1 2 3\n  T1 takes A\n  T2 takes B\n  T3 takes C\n  T1 waits for B, held by T2\n  T2 waits for C, held by T3\n  T3 waits for A, held by T1\nDeadlock: T1 waits for B held by T2; T2 waits for C held by T3; T3 waits for A held by T1. No turn of theirs can ever move.\n\n1) threads  2) run a schedule  3) every interleaving  4) other threads  5) quit\nchoice> 3\nThree threads of four steps have 34,650 interleavings, too many to run here. Every one was run in the cross-check:\n  14,400 of them end in deadlock; the 6 serial ones do not.\n\n1) threads  2) run a schedule  3) every interleaving  4) other threads  5) quit\nchoice> 4\nthreads (opposite, ordered, ring or ring-ordered)> ring-ordered\nThreads (ring-ordered):\n  T1: lock A, lock B, unlock B, unlock A\n  T2: lock B, lock C, unlock C, unlock B\n  T3: lock A, lock C, unlock C, unlock A\n\n1) threads  2) run a schedule  3) every interleaving  4) other threads  5) quit\nchoice> 2\nA schedule: whose turn it is, such as 1 2 1 2 (threads 1 to 3, up to 30 turns).\nschedule> 1 2 3 1 2 3 1 2 3 1 2 3\n  T1 takes A\n  T2 takes B\n  T3 waits for A, held by T1\n  T1 waits for B, held by T2\n  T2 takes C\n  T3 waits for A, held by T1\n  T1 waits for B, held by T2\n  T2 releases C\n  T3 waits for A, held by T1\n  T1 waits for B, held by T2\n  T2 releases B\n  T3 waits for A, held by T1\nNo deadlock; 2 threads still to finish, and running the rest, lowest thread first, finishes every thread.\n\n1) threads  2) run a schedule  3) every interleaving  4) other threads  5) quit\nchoice> 3\nThree threads of four steps have 34,650 interleavings, too many to run here. Every one was run in the cross-check:\n  none of them ends in deadlock: every thread takes its locks in the order A, B, C.\n\n1) threads  2) run a schedule  3) every interleaving  4) other threads  5) quit\nchoice> 5\nBye.\n",
      "interpreter": "equal"
    }
  ],
  "builtOn": [
    {
      "slug": "deadlock-by-lock-order",
      "caseId": "251-deadlock-by-lock-order",
      "title": "Deadlock by lock order — the defect that exists only in the pair"
    }
  ],
  "updated": "2026-10-16"
}
