kahn-queue 0.1.1__tar.gz → 1.0.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {kahn_queue-0.1.1/src/kahn_queue.egg-info → kahn_queue-1.0.1}/PKG-INFO +33 -4
- kahn_queue-0.1.1/PKG-INFO → kahn_queue-1.0.1/README.md +29 -11
- {kahn_queue-0.1.1 → kahn_queue-1.0.1}/pyproject.toml +16 -4
- {kahn_queue-0.1.1 → kahn_queue-1.0.1}/src/kahnQueue/concurrent_kahn_queue.py +11 -12
- {kahn_queue-0.1.1 → kahn_queue-1.0.1}/src/kahnQueue/default_kahn_queue.py +10 -10
- kahn_queue-0.1.1/README.md → kahn_queue-1.0.1/src/kahn_queue.egg-info/PKG-INFO +40 -0
- {kahn_queue-0.1.1 → kahn_queue-1.0.1}/tests/test_concurrent_kahn_queue.py +26 -26
- {kahn_queue-0.1.1 → kahn_queue-1.0.1}/tests/test_default_kahn_queue.py +15 -15
- {kahn_queue-0.1.1 → kahn_queue-1.0.1}/setup.cfg +0 -0
- {kahn_queue-0.1.1 → kahn_queue-1.0.1}/src/dag.py +0 -0
- {kahn_queue-0.1.1 → kahn_queue-1.0.1}/src/exception.py +0 -0
- {kahn_queue-0.1.1 → kahn_queue-1.0.1}/src/kahnQueue/__init__.py +0 -0
- {kahn_queue-0.1.1 → kahn_queue-1.0.1}/src/kahnQueue/kahn_queue.py +0 -0
- {kahn_queue-0.1.1 → kahn_queue-1.0.1}/src/kahnQueue/node_machine.py +0 -0
- {kahn_queue-0.1.1 → kahn_queue-1.0.1}/src/kahnQueue/node_state.py +0 -0
- {kahn_queue-0.1.1 → kahn_queue-1.0.1}/src/kahn_queue.egg-info/SOURCES.txt +0 -0
- {kahn_queue-0.1.1 → kahn_queue-1.0.1}/src/kahn_queue.egg-info/dependency_links.txt +0 -0
- {kahn_queue-0.1.1 → kahn_queue-1.0.1}/src/kahn_queue.egg-info/top_level.txt +0 -0
- {kahn_queue-0.1.1 → kahn_queue-1.0.1}/src/scheduler.py +0 -0
- {kahn_queue-0.1.1 → kahn_queue-1.0.1}/src/tracker.py +0 -0
- {kahn_queue-0.1.1 → kahn_queue-1.0.1}/src/utils/__init__.py +0 -0
- {kahn_queue-0.1.1 → kahn_queue-1.0.1}/src/utils/state_machine.py +0 -0
- {kahn_queue-0.1.1 → kahn_queue-1.0.1}/tests/test_dag.py +0 -0
- {kahn_queue-0.1.1 → kahn_queue-1.0.1}/tests/test_kahn_scheduler.py +0 -0
- {kahn_queue-0.1.1 → kahn_queue-1.0.1}/tests/test_node_machine.py +0 -0
- {kahn_queue-0.1.1 → kahn_queue-1.0.1}/tests/test_node_progress_tracker.py +0 -0
- {kahn_queue-0.1.1 → kahn_queue-1.0.1}/tests/test_state_machine.py +0 -0
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: kahn-queue
|
|
3
|
-
Version: 0.1
|
|
4
|
-
Summary: Kahn-
|
|
3
|
+
Version: 1.0.1
|
|
4
|
+
Summary: Lightweight Kahn-based ready queue for dependency-driven scheduling and workflows
|
|
5
5
|
License: MIT
|
|
6
|
-
Project-URL: Homepage, https://github.
|
|
6
|
+
Project-URL: Homepage, https://flashlock.github.io/kahn-queue/
|
|
7
7
|
Project-URL: Repository, https://github.com/Flashlock/kahn-queue
|
|
8
|
-
Keywords:
|
|
8
|
+
Keywords: kahn queue,kahn-style scheduler,topological sort queue,dynamic topological sort,ready queue,dependency scheduler,dag scheduler,incremental topological sort,agent orchestration,parallel task scheduling,deterministic scheduler
|
|
9
9
|
Requires-Python: >=3.10
|
|
10
10
|
Description-Content-Type: text/markdown
|
|
11
11
|
|
|
@@ -84,3 +84,32 @@ result = sched.get_result()
|
|
|
84
84
|
# result.completed, result.failed, result.pruned — frozensets of ids
|
|
85
85
|
done = sched.is_finished
|
|
86
86
|
```
|
|
87
|
+
|
|
88
|
+
### Manual KahnQueue
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
from dag import Dag
|
|
92
|
+
from kahnQueue.default_kahn_queue import DefaultKahnQueue
|
|
93
|
+
|
|
94
|
+
b = Dag.builder()
|
|
95
|
+
lint = b.add("lint")
|
|
96
|
+
compile = b.add("compile")
|
|
97
|
+
test = b.add("test")
|
|
98
|
+
b.connect(lint, compile).connect(compile, test)
|
|
99
|
+
dag = b.build()
|
|
100
|
+
|
|
101
|
+
q = DefaultKahnQueue(dag)
|
|
102
|
+
|
|
103
|
+
ready = list(q.ready_ids())
|
|
104
|
+
|
|
105
|
+
while ready:
|
|
106
|
+
id_ = ready.pop(0)
|
|
107
|
+
|
|
108
|
+
# do work for `id_` (e.g. run_step(dag[id_]))
|
|
109
|
+
|
|
110
|
+
promoted = q.pop(id_)
|
|
111
|
+
ready.extend(promoted)
|
|
112
|
+
|
|
113
|
+
# If a node fails, you can prune it (and its descendants):
|
|
114
|
+
# q.prune(id_)
|
|
115
|
+
```
|
|
@@ -1,14 +1,3 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: kahn-queue
|
|
3
|
-
Version: 0.1.1
|
|
4
|
-
Summary: Kahn-style ready-queue for dependency scheduling
|
|
5
|
-
License: MIT
|
|
6
|
-
Project-URL: Homepage, https://github.com/Flashlock/kahn-queue
|
|
7
|
-
Project-URL: Repository, https://github.com/Flashlock/kahn-queue
|
|
8
|
-
Keywords: dag,scheduler,workflow,kahn
|
|
9
|
-
Requires-Python: >=3.10
|
|
10
|
-
Description-Content-Type: text/markdown
|
|
11
|
-
|
|
12
1
|
# kahn-queue (Python)
|
|
13
2
|
|
|
14
3
|
## Getting started
|
|
@@ -84,3 +73,32 @@ result = sched.get_result()
|
|
|
84
73
|
# result.completed, result.failed, result.pruned — frozensets of ids
|
|
85
74
|
done = sched.is_finished
|
|
86
75
|
```
|
|
76
|
+
|
|
77
|
+
### Manual KahnQueue
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
from dag import Dag
|
|
81
|
+
from kahnQueue.default_kahn_queue import DefaultKahnQueue
|
|
82
|
+
|
|
83
|
+
b = Dag.builder()
|
|
84
|
+
lint = b.add("lint")
|
|
85
|
+
compile = b.add("compile")
|
|
86
|
+
test = b.add("test")
|
|
87
|
+
b.connect(lint, compile).connect(compile, test)
|
|
88
|
+
dag = b.build()
|
|
89
|
+
|
|
90
|
+
q = DefaultKahnQueue(dag)
|
|
91
|
+
|
|
92
|
+
ready = list(q.ready_ids())
|
|
93
|
+
|
|
94
|
+
while ready:
|
|
95
|
+
id_ = ready.pop(0)
|
|
96
|
+
|
|
97
|
+
# do work for `id_` (e.g. run_step(dag[id_]))
|
|
98
|
+
|
|
99
|
+
promoted = q.pop(id_)
|
|
100
|
+
ready.extend(promoted)
|
|
101
|
+
|
|
102
|
+
# If a node fails, you can prune it (and its descendants):
|
|
103
|
+
# q.prune(id_)
|
|
104
|
+
```
|
|
@@ -4,15 +4,27 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "kahn-queue"
|
|
7
|
-
version = "0.1
|
|
8
|
-
description = "Kahn-
|
|
7
|
+
version = "1.0.1"
|
|
8
|
+
description = "Lightweight Kahn-based ready queue for dependency-driven scheduling and workflows"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
11
11
|
license = { text = "MIT" }
|
|
12
|
-
keywords = [
|
|
12
|
+
keywords = [
|
|
13
|
+
"kahn queue",
|
|
14
|
+
"kahn-style scheduler",
|
|
15
|
+
"topological sort queue",
|
|
16
|
+
"dynamic topological sort",
|
|
17
|
+
"ready queue",
|
|
18
|
+
"dependency scheduler",
|
|
19
|
+
"dag scheduler",
|
|
20
|
+
"incremental topological sort",
|
|
21
|
+
"agent orchestration",
|
|
22
|
+
"parallel task scheduling",
|
|
23
|
+
"deterministic scheduler",
|
|
24
|
+
]
|
|
13
25
|
|
|
14
26
|
[project.urls]
|
|
15
|
-
Homepage = "https://github.
|
|
27
|
+
Homepage = "https://flashlock.github.io/kahn-queue/"
|
|
16
28
|
Repository = "https://github.com/Flashlock/kahn-queue"
|
|
17
29
|
|
|
18
30
|
# Layout: packages under src/ plus top-level modules (dag, scheduler, …).
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from threading import RLock
|
|
4
|
-
from typing import List
|
|
4
|
+
from typing import List
|
|
5
5
|
|
|
6
6
|
from dag import Dag, validate_node
|
|
7
7
|
from kahnQueue.node_machine import NodeMachine
|
|
@@ -25,7 +25,7 @@ class ConcurrentKahnQueue(KahnQueue):
|
|
|
25
25
|
]
|
|
26
26
|
self._locks = [RLock() for _ in range(dag.size())]
|
|
27
27
|
|
|
28
|
-
def pop(self, id: int) ->
|
|
28
|
+
def pop(self, id: int) -> List[int]:
|
|
29
29
|
validate_node(id, self._dag.size())
|
|
30
30
|
|
|
31
31
|
with self._locks[id]:
|
|
@@ -35,7 +35,7 @@ class ConcurrentKahnQueue(KahnQueue):
|
|
|
35
35
|
|
|
36
36
|
machine.transition(NodeState.COMPLETE)
|
|
37
37
|
|
|
38
|
-
promoted:
|
|
38
|
+
promoted: set[int] = set()
|
|
39
39
|
for cid in self._dag.targets(id):
|
|
40
40
|
with self._locks[cid]:
|
|
41
41
|
child = self._node_machines[cid]
|
|
@@ -44,12 +44,12 @@ class ConcurrentKahnQueue(KahnQueue):
|
|
|
44
44
|
if child.can_transition(NodeState.ACTIVE):
|
|
45
45
|
child.transition(NodeState.ACTIVE)
|
|
46
46
|
promoted.add(cid)
|
|
47
|
-
return promoted
|
|
47
|
+
return sorted(promoted)
|
|
48
48
|
|
|
49
|
-
def prune(self, id: int) ->
|
|
49
|
+
def prune(self, id: int) -> List[int]:
|
|
50
50
|
validate_node(id, self._dag.size())
|
|
51
51
|
|
|
52
|
-
affected:
|
|
52
|
+
affected: set[int] = set()
|
|
53
53
|
stack: List[int] = [id]
|
|
54
54
|
|
|
55
55
|
while stack:
|
|
@@ -66,10 +66,9 @@ class ConcurrentKahnQueue(KahnQueue):
|
|
|
66
66
|
# Adding targets outside the lock is safe as the DAG structure is immutable
|
|
67
67
|
stack.extend(self._dag.targets(curr))
|
|
68
68
|
|
|
69
|
-
return affected
|
|
69
|
+
return sorted(affected)
|
|
70
70
|
|
|
71
|
-
def ready_ids(self) ->
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
}
|
|
71
|
+
def ready_ids(self) -> List[int]:
|
|
72
|
+
# Deterministic ordering for sequential callers; may not reflect a consistent snapshot
|
|
73
|
+
# under concurrent updates.
|
|
74
|
+
return [m.id for m in self._node_machines if m.is_(NodeState.READY)]
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from
|
|
4
|
-
from typing import List, Set
|
|
3
|
+
from typing import List
|
|
5
4
|
|
|
6
5
|
from dag import Dag, validate_node
|
|
7
6
|
from kahnQueue.node_machine import NodeMachine
|
|
@@ -17,7 +16,7 @@ class DefaultKahnQueue(KahnQueue):
|
|
|
17
16
|
NodeMachine.create(i, dag.in_degree(i)) for i in range(dag.size())
|
|
18
17
|
]
|
|
19
18
|
|
|
20
|
-
def pop(self, id: int) ->
|
|
19
|
+
def pop(self, id: int) -> List[int]:
|
|
21
20
|
validate_node(id, self._dag.size())
|
|
22
21
|
machine = self._node_machines[id]
|
|
23
22
|
|
|
@@ -26,7 +25,7 @@ class DefaultKahnQueue(KahnQueue):
|
|
|
26
25
|
|
|
27
26
|
machine.transition(NodeState.COMPLETE)
|
|
28
27
|
|
|
29
|
-
promoted:
|
|
28
|
+
promoted: List[int] = []
|
|
30
29
|
for cid in self._dag.targets(id):
|
|
31
30
|
child = self._node_machines[cid]
|
|
32
31
|
child.decrement()
|
|
@@ -34,12 +33,12 @@ class DefaultKahnQueue(KahnQueue):
|
|
|
34
33
|
# in a single-threaded queue.
|
|
35
34
|
if child.is_(NodeState.READY):
|
|
36
35
|
child.transition(NodeState.ACTIVE)
|
|
37
|
-
promoted.
|
|
36
|
+
promoted.append(cid)
|
|
38
37
|
return promoted
|
|
39
38
|
|
|
40
|
-
def prune(self, id: int) ->
|
|
39
|
+
def prune(self, id: int) -> List[int]:
|
|
41
40
|
validate_node(id, self._dag.size())
|
|
42
|
-
affected:
|
|
41
|
+
affected: set[int] = set()
|
|
43
42
|
stack: List[int] = [id]
|
|
44
43
|
|
|
45
44
|
while stack:
|
|
@@ -54,7 +53,8 @@ class DefaultKahnQueue(KahnQueue):
|
|
|
54
53
|
affected.add(curr)
|
|
55
54
|
stack.extend(self._dag.targets(curr))
|
|
56
55
|
|
|
57
|
-
return affected
|
|
56
|
+
return sorted(affected)
|
|
58
57
|
|
|
59
|
-
def ready_ids(self) ->
|
|
60
|
-
|
|
58
|
+
def ready_ids(self) -> List[int]:
|
|
59
|
+
# Deterministic: ids are scanned in ascending order.
|
|
60
|
+
return [m.id for m in self._node_machines if m.is_(NodeState.READY)]
|
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: kahn-queue
|
|
3
|
+
Version: 1.0.1
|
|
4
|
+
Summary: Lightweight Kahn-based ready queue for dependency-driven scheduling and workflows
|
|
5
|
+
License: MIT
|
|
6
|
+
Project-URL: Homepage, https://flashlock.github.io/kahn-queue/
|
|
7
|
+
Project-URL: Repository, https://github.com/Flashlock/kahn-queue
|
|
8
|
+
Keywords: kahn queue,kahn-style scheduler,topological sort queue,dynamic topological sort,ready queue,dependency scheduler,dag scheduler,incremental topological sort,agent orchestration,parallel task scheduling,deterministic scheduler
|
|
9
|
+
Requires-Python: >=3.10
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
|
|
1
12
|
# kahn-queue (Python)
|
|
2
13
|
|
|
3
14
|
## Getting started
|
|
@@ -73,3 +84,32 @@ result = sched.get_result()
|
|
|
73
84
|
# result.completed, result.failed, result.pruned — frozensets of ids
|
|
74
85
|
done = sched.is_finished
|
|
75
86
|
```
|
|
87
|
+
|
|
88
|
+
### Manual KahnQueue
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
from dag import Dag
|
|
92
|
+
from kahnQueue.default_kahn_queue import DefaultKahnQueue
|
|
93
|
+
|
|
94
|
+
b = Dag.builder()
|
|
95
|
+
lint = b.add("lint")
|
|
96
|
+
compile = b.add("compile")
|
|
97
|
+
test = b.add("test")
|
|
98
|
+
b.connect(lint, compile).connect(compile, test)
|
|
99
|
+
dag = b.build()
|
|
100
|
+
|
|
101
|
+
q = DefaultKahnQueue(dag)
|
|
102
|
+
|
|
103
|
+
ready = list(q.ready_ids())
|
|
104
|
+
|
|
105
|
+
while ready:
|
|
106
|
+
id_ = ready.pop(0)
|
|
107
|
+
|
|
108
|
+
# do work for `id_` (e.g. run_step(dag[id_]))
|
|
109
|
+
|
|
110
|
+
promoted = q.pop(id_)
|
|
111
|
+
ready.extend(promoted)
|
|
112
|
+
|
|
113
|
+
# If a node fails, you can prune it (and its descendants):
|
|
114
|
+
# q.prune(id_)
|
|
115
|
+
```
|
|
@@ -17,7 +17,7 @@ def _force_node_state(q: ConcurrentKahnQueue, id_: int, state: NodeState) -> Non
|
|
|
17
17
|
def test_basics_ready_ids_and_pop_validation():
|
|
18
18
|
empty = Dag.builder().build()
|
|
19
19
|
q0 = ConcurrentKahnQueue(empty)
|
|
20
|
-
assert q0.ready_ids() ==
|
|
20
|
+
assert q0.ready_ids() == []
|
|
21
21
|
with pytest.raises(IndexError):
|
|
22
22
|
q0.pop(0)
|
|
23
23
|
|
|
@@ -28,7 +28,7 @@ def test_basics_ready_ids_and_pop_validation():
|
|
|
28
28
|
chain.connect(root, mid).connect(mid, leaf)
|
|
29
29
|
dag_chain = chain.build()
|
|
30
30
|
qc = ConcurrentKahnQueue(dag_chain)
|
|
31
|
-
assert qc.ready_ids() ==
|
|
31
|
+
assert qc.ready_ids() == [root]
|
|
32
32
|
|
|
33
33
|
join = Dag.builder()
|
|
34
34
|
a = join.add("a")
|
|
@@ -37,13 +37,13 @@ def test_basics_ready_ids_and_pop_validation():
|
|
|
37
37
|
join.connect(a, jn).connect(c, jn)
|
|
38
38
|
dag_join = join.build()
|
|
39
39
|
qj = ConcurrentKahnQueue(dag_join)
|
|
40
|
-
assert qj.ready_ids() ==
|
|
40
|
+
assert qj.ready_ids() == [a, c]
|
|
41
41
|
|
|
42
42
|
one = Dag.builder()
|
|
43
43
|
only = one.add("x")
|
|
44
44
|
dag_one = one.build()
|
|
45
45
|
q1 = ConcurrentKahnQueue(dag_one)
|
|
46
|
-
assert q1.ready_ids() ==
|
|
46
|
+
assert q1.ready_ids() == [only]
|
|
47
47
|
with pytest.raises(ValueError) as ex:
|
|
48
48
|
q1.pop(only)
|
|
49
49
|
assert "Pop failed. Node" in str(ex.value)
|
|
@@ -64,7 +64,7 @@ def test_prune_collects_reachable_in_linear_and_fork_shapes():
|
|
|
64
64
|
l = b1.add("l")
|
|
65
65
|
b1.connect(r, m).connect(m, l)
|
|
66
66
|
q1 = ConcurrentKahnQueue(b1.build())
|
|
67
|
-
assert q1.prune(r) == {r, m, l}
|
|
67
|
+
assert set(q1.prune(r)) == {r, m, l}
|
|
68
68
|
|
|
69
69
|
b2 = Dag.builder()
|
|
70
70
|
root = b2.add("root")
|
|
@@ -72,7 +72,7 @@ def test_prune_collects_reachable_in_linear_and_fork_shapes():
|
|
|
72
72
|
right = b2.add("right")
|
|
73
73
|
b2.connect(root, left).connect(root, right)
|
|
74
74
|
q2 = ConcurrentKahnQueue(b2.build())
|
|
75
|
-
assert q2.prune(root) == {root, left, right}
|
|
75
|
+
assert set(q2.prune(root)) == {root, left, right}
|
|
76
76
|
|
|
77
77
|
|
|
78
78
|
def test_prune_updates_ready_set():
|
|
@@ -83,9 +83,9 @@ def test_prune_updates_ready_set():
|
|
|
83
83
|
b.connect(a, join).connect(c, join)
|
|
84
84
|
dag = b.build()
|
|
85
85
|
q = ConcurrentKahnQueue(dag)
|
|
86
|
-
assert q.ready_ids() ==
|
|
86
|
+
assert q.ready_ids() == [a, c]
|
|
87
87
|
q.prune(a)
|
|
88
|
-
assert q.ready_ids() ==
|
|
88
|
+
assert q.ready_ids() == [c]
|
|
89
89
|
|
|
90
90
|
|
|
91
91
|
def test_ready_ids_stable_under_sequential_repeats():
|
|
@@ -108,10 +108,10 @@ def test_prune_second_call_throws():
|
|
|
108
108
|
r = b.add("r")
|
|
109
109
|
dag = b.build()
|
|
110
110
|
q = ConcurrentKahnQueue(dag)
|
|
111
|
-
assert q.prune(r) ==
|
|
112
|
-
assert q.ready_ids() ==
|
|
111
|
+
assert q.prune(r) == [r]
|
|
112
|
+
assert q.ready_ids() == []
|
|
113
113
|
# ConcurrentKahnQueue.prune is idempotent for already-pruned branches.
|
|
114
|
-
assert q.prune(r) ==
|
|
114
|
+
assert q.prune(r) == []
|
|
115
115
|
for id_ in q.ready_ids():
|
|
116
116
|
Dag.validate_node(id_, dag.size())
|
|
117
117
|
|
|
@@ -125,10 +125,10 @@ def test_kahn_progression_pop_active_node_returns_promoted_dependents():
|
|
|
125
125
|
dag = b.build()
|
|
126
126
|
q = ConcurrentKahnQueue(dag)
|
|
127
127
|
_force_node_state(q, root, NodeState.ACTIVE)
|
|
128
|
-
assert q.pop(root) ==
|
|
129
|
-
assert q.pop(mid) ==
|
|
130
|
-
assert q.pop(leaf) ==
|
|
131
|
-
assert q.ready_ids() ==
|
|
128
|
+
assert q.pop(root) == [mid]
|
|
129
|
+
assert q.pop(mid) == [leaf]
|
|
130
|
+
assert q.pop(leaf) == []
|
|
131
|
+
assert q.ready_ids() == []
|
|
132
132
|
for id_ in q.ready_ids():
|
|
133
133
|
Dag.validate_node(id_, dag.size())
|
|
134
134
|
|
|
@@ -141,7 +141,7 @@ def test_concurrent_ready_ids_stress_reads_match_snapshot():
|
|
|
141
141
|
b.connect(x, y)
|
|
142
142
|
dag = b.build()
|
|
143
143
|
q = ConcurrentKahnQueue(dag)
|
|
144
|
-
expected =
|
|
144
|
+
expected = [x]
|
|
145
145
|
assert q.ready_ids() == expected
|
|
146
146
|
|
|
147
147
|
def worker():
|
|
@@ -166,9 +166,9 @@ def test_concurrent_disjoint_prune():
|
|
|
166
166
|
with concurrent.futures.ThreadPoolExecutor(max_workers=2) as pool:
|
|
167
167
|
f1 = pool.submit(q.prune, left)
|
|
168
168
|
f2 = pool.submit(q.prune, right)
|
|
169
|
-
assert f1.result() ==
|
|
170
|
-
assert f2.result() ==
|
|
171
|
-
assert q.ready_ids() ==
|
|
169
|
+
assert f1.result() == [left]
|
|
170
|
+
assert f2.result() == [right]
|
|
171
|
+
assert q.ready_ids() == []
|
|
172
172
|
|
|
173
173
|
|
|
174
174
|
def test_concurrent_pop_failures_do_not_mutate_ready():
|
|
@@ -208,9 +208,9 @@ def test_concurrent_same_id_prune_contention():
|
|
|
208
208
|
futures = [pool.submit(worker) for _ in range(8)]
|
|
209
209
|
start.set()
|
|
210
210
|
results = [f.result(timeout=30) for f in futures]
|
|
211
|
-
assert results.count(
|
|
212
|
-
assert results.count(
|
|
213
|
-
assert q.ready_ids() ==
|
|
211
|
+
assert results.count([root]) == 1
|
|
212
|
+
assert results.count([]) == 7
|
|
213
|
+
assert q.ready_ids() == []
|
|
214
214
|
|
|
215
215
|
|
|
216
216
|
def test_concurrent_overlapping_prune():
|
|
@@ -240,9 +240,9 @@ def test_concurrent_overlapping_prune():
|
|
|
240
240
|
|
|
241
241
|
assert set(s1) | set(s2) == {r, m, l}
|
|
242
242
|
|
|
243
|
-
any_full = (s1 == {r, m, l}) or (s2 == {r, m, l})
|
|
243
|
+
any_full = (set(s1) == {r, m, l}) or (set(s2) == {r, m, l})
|
|
244
244
|
if any_full:
|
|
245
|
-
assert q.ready_ids() ==
|
|
245
|
+
assert q.ready_ids() == []
|
|
246
246
|
|
|
247
247
|
|
|
248
248
|
def test_prune_mutation_visible_to_other_thread_after_future_get():
|
|
@@ -253,8 +253,8 @@ def test_prune_mutation_visible_to_other_thread_after_future_get():
|
|
|
253
253
|
q = ConcurrentKahnQueue(dag)
|
|
254
254
|
with concurrent.futures.ThreadPoolExecutor(max_workers=1) as pool:
|
|
255
255
|
done = pool.submit(q.prune, left)
|
|
256
|
-
assert done.result(timeout=10) ==
|
|
257
|
-
assert q.ready_ids() ==
|
|
256
|
+
assert done.result(timeout=10) == [left]
|
|
257
|
+
assert q.ready_ids() == [right]
|
|
258
258
|
|
|
259
259
|
|
|
260
260
|
def _get_prune_result_or_illegal_state(fut: "concurrent.futures.Future[set[int]]"):
|
|
@@ -12,7 +12,7 @@ def _force_node_state(q: DefaultKahnQueue, id_: int, state: NodeState) -> None:
|
|
|
12
12
|
def test_empty_dag_ready_ids_empty_and_pop_rejects_invalid_id():
|
|
13
13
|
dag = Dag.builder().build()
|
|
14
14
|
q = DefaultKahnQueue(dag)
|
|
15
|
-
assert q.ready_ids() ==
|
|
15
|
+
assert q.ready_ids() == []
|
|
16
16
|
with pytest.raises(IndexError):
|
|
17
17
|
q.pop(0)
|
|
18
18
|
|
|
@@ -25,7 +25,7 @@ def test_ready_ids_contains_only_zero_in_degree_nodes():
|
|
|
25
25
|
b.connect(root, mid).connect(mid, leaf)
|
|
26
26
|
dag = b.build()
|
|
27
27
|
q = DefaultKahnQueue(dag)
|
|
28
|
-
assert q.ready_ids() ==
|
|
28
|
+
assert q.ready_ids() == [root]
|
|
29
29
|
|
|
30
30
|
|
|
31
31
|
def test_ready_ids_two_independent_roots():
|
|
@@ -36,7 +36,7 @@ def test_ready_ids_two_independent_roots():
|
|
|
36
36
|
b.connect(a, join).connect(c, join)
|
|
37
37
|
dag = b.build()
|
|
38
38
|
q = DefaultKahnQueue(dag)
|
|
39
|
-
assert q.ready_ids() ==
|
|
39
|
+
assert q.ready_ids() == [a, c]
|
|
40
40
|
|
|
41
41
|
|
|
42
42
|
def test_pop_throws_when_node_is_ready_not_active():
|
|
@@ -44,7 +44,7 @@ def test_pop_throws_when_node_is_ready_not_active():
|
|
|
44
44
|
only = b.add("x")
|
|
45
45
|
dag = b.build()
|
|
46
46
|
q = DefaultKahnQueue(dag)
|
|
47
|
-
assert q.ready_ids() ==
|
|
47
|
+
assert q.ready_ids() == [only]
|
|
48
48
|
with pytest.raises(ValueError) as ex:
|
|
49
49
|
q.pop(only)
|
|
50
50
|
assert "Pop failed. Node" in str(ex.value)
|
|
@@ -68,7 +68,7 @@ def test_prune_marks_root_and_reachable_descendants():
|
|
|
68
68
|
b.connect(r, m).connect(m, l)
|
|
69
69
|
dag = b.build()
|
|
70
70
|
q = DefaultKahnQueue(dag)
|
|
71
|
-
assert q.prune(r) == {r, m, l}
|
|
71
|
+
assert set(q.prune(r)) == {r, m, l}
|
|
72
72
|
|
|
73
73
|
|
|
74
74
|
def test_prune_fork_collects_all_branches():
|
|
@@ -79,7 +79,7 @@ def test_prune_fork_collects_all_branches():
|
|
|
79
79
|
b.connect(root, left).connect(root, right)
|
|
80
80
|
dag = b.build()
|
|
81
81
|
q = DefaultKahnQueue(dag)
|
|
82
|
-
assert q.prune(root) == {root, left, right}
|
|
82
|
+
assert set(q.prune(root)) == {root, left, right}
|
|
83
83
|
|
|
84
84
|
|
|
85
85
|
def test_prune_removes_ids_from_ready_set():
|
|
@@ -90,9 +90,9 @@ def test_prune_removes_ids_from_ready_set():
|
|
|
90
90
|
b.connect(a, join).connect(c, join)
|
|
91
91
|
dag = b.build()
|
|
92
92
|
q = DefaultKahnQueue(dag)
|
|
93
|
-
assert q.ready_ids() ==
|
|
93
|
+
assert q.ready_ids() == [a, c]
|
|
94
94
|
q.prune(a)
|
|
95
|
-
assert q.ready_ids() ==
|
|
95
|
+
assert q.ready_ids() == [c]
|
|
96
96
|
|
|
97
97
|
|
|
98
98
|
def test_prune_second_call_throws():
|
|
@@ -100,10 +100,10 @@ def test_prune_second_call_throws():
|
|
|
100
100
|
r = b.add("r")
|
|
101
101
|
dag = b.build()
|
|
102
102
|
q = DefaultKahnQueue(dag)
|
|
103
|
-
assert q.prune(r) ==
|
|
104
|
-
assert q.ready_ids() ==
|
|
103
|
+
assert q.prune(r) == [r]
|
|
104
|
+
assert q.ready_ids() == []
|
|
105
105
|
# DefaultKahnQueue.prune is idempotent for already-pruned branches.
|
|
106
|
-
assert q.prune(r) ==
|
|
106
|
+
assert q.prune(r) == []
|
|
107
107
|
|
|
108
108
|
|
|
109
109
|
def test_kahn_progression_pop_active_node_returns_promoted_dependents():
|
|
@@ -116,8 +116,8 @@ def test_kahn_progression_pop_active_node_returns_promoted_dependents():
|
|
|
116
116
|
q = DefaultKahnQueue(dag)
|
|
117
117
|
|
|
118
118
|
_force_node_state(q, root, NodeState.ACTIVE)
|
|
119
|
-
assert q.pop(root) ==
|
|
120
|
-
assert q.pop(mid) ==
|
|
121
|
-
assert q.pop(leaf) ==
|
|
122
|
-
assert q.ready_ids() ==
|
|
119
|
+
assert q.pop(root) == [mid]
|
|
120
|
+
assert q.pop(mid) == [leaf]
|
|
121
|
+
assert q.pop(leaf) == []
|
|
122
|
+
assert q.ready_ids() == []
|
|
123
123
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|