Skip to content

Commit 96f95c3

Browse files
committed
test(stream): validate P07.01 combined with P07.04 (OpenDCAI#550) and merged OpenDCAI#551
1 parent 2cdd87d commit 96f95c3

5 files changed

Lines changed: 800 additions & 1 deletion

File tree

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
name: P07.01 transactional resume validation
2+
3+
on:
4+
push:
5+
branches:
6+
- validation/p07-01-with-pr550
7+
workflow_dispatch:
8+
9+
jobs:
10+
contract:
11+
strategy:
12+
fail-fast: false
13+
matrix:
14+
include:
15+
- os: ubuntu-latest
16+
python: "3.10"
17+
- os: ubuntu-latest
18+
python: "3.12"
19+
- os: windows-latest
20+
python: "3.10"
21+
- os: windows-latest
22+
python: "3.12"
23+
runs-on: ${{ matrix.os }}
24+
steps:
25+
- uses: actions/checkout@v4
26+
- uses: actions/setup-python@v5
27+
with:
28+
python-version: ${{ matrix.python }}
29+
- uses: astral-sh/setup-uv@v7
30+
- name: Install dependencies
31+
env:
32+
UV_SYSTEM_PYTHON: "1"
33+
run: |
34+
uv pip install --no-cache -r requirements.txt
35+
uv pip install -e .[test]
36+
- name: P07.01 focused tests
37+
run: |
38+
python -m pytest -q test/cpu_only/test_stream_transactional_resume.py
39+
- name: CPU/API suite
40+
env:
41+
PYTHONDONTWRITEBYTECODE: "1"
42+
run: |
43+
python -m pytest -m "cpu or api"
44+
- name: Syntax compile
45+
run: |
46+
python -m compileall -q dataflow/pipeline/Pipeline.py dataflow/utils/storage.py dataflow/utils/stream_transaction.py test/cpu_only/test_stream_transactional_resume.py

‎dataflow/pipeline/Pipeline.py‎

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -625,7 +625,64 @@ class StreamBatchedPipelineABC(BatchedPipelineABC):
625625
def __init__(self):
626626
super().__init__()
627627

628-
def _compiled_forward(self, resume_step: int=0, batch_size: int|None=None, resume_from_last: bool=True):
628+
def _compiled_forward(self, resume_step: int=0, batch_size: int|None=None, resume_from_last: bool=True, transactional_resume: bool=False):
629+
if transactional_resume:
630+
if not resume_from_last or resume_step != 0:
631+
raise ValueError("Transactional streaming requires resume_from_last=True and resume_step=0")
632+
return self._transactional_forward(batch_size)
633+
return self._legacy_stream_forward(resume_step, batch_size, resume_from_last)
634+
635+
def _transactional_forward(self, batch_size):
636+
from dataflow.utils.stream_transaction import StreamTransactionCoordinator
637+
from dataflow.utils.stream_transaction import TransactionError
638+
639+
if not self.compiled:
640+
raise RuntimeError("Pipeline is not compiled yet")
641+
nodes = [node for node in self.op_nodes_list if node.op_obj is not None]
642+
with StreamTransactionCoordinator(nodes, batch_size) as coordinator:
643+
while coordinator.manifest["stage"] < len(nodes):
644+
stage = coordinator.manifest["stage"]
645+
node = nodes[stage]
646+
storage = node.storage
647+
storage.batch_size = batch_size
648+
batch = coordinator.manifest["batch"]
649+
count = coordinator.input_rows(stage)
650+
total_batches = (count + batch_size - 1) // batch_size
651+
if batch > total_batches:
652+
raise TransactionError("Committed cursor exceeds stage input")
653+
if batch == total_batches:
654+
coordinator.transition(stage)
655+
continue
656+
storage.batch_step = batch
657+
stream = storage.iter_chunks()
658+
try:
659+
for _ in range(batch):
660+
if next(stream, None) is None:
661+
raise TransactionError("Input stream shorter than committed cursor")
662+
chunk = next(stream, None)
663+
finally:
664+
if "chunk" not in locals() and hasattr(stream, "close"):
665+
stream.close()
666+
if chunk is None:
667+
raise TransactionError("Input stream shorter than manifest row count")
668+
storage._current_streaming_chunk = chunk
669+
coordinator.begin(stage, batch)
670+
try:
671+
node.op_obj.run(storage=storage, **node.kwargs)
672+
coordinator.commit(stage, batch)
673+
except BaseException:
674+
coordinator.abort()
675+
raise
676+
finally:
677+
storage._current_streaming_chunk = None
678+
if hasattr(stream, "close"):
679+
stream.close()
680+
if "chunk" in locals():
681+
del chunk
682+
storage.batch_step = coordinator.manifest["batch"]
683+
return None
684+
685+
def _legacy_stream_forward(self, resume_step: int=0, batch_size: int|None=None, resume_from_last: bool=True):
629686
"""
630687
resume_step (int): resume inference from this step
631688
batch_size (int|None): if set, run the pipeline in batch mode with this batch size

‎dataflow/utils/storage.py‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1013,6 +1013,12 @@ def read(self, output_type: Literal["dataframe", "dict"]="dataframe") -> Any:
10131013
return self._convert_output(dataframe, output_type)
10141014

10151015
def write(self, data: Any) -> Any:
1016+
transaction = getattr(self, "_stream_transaction", None)
1017+
if transaction is not None:
1018+
return transaction.write(self, data)
1019+
return self._legacy_batched_write(data)
1020+
1021+
def _legacy_batched_write(self, data: Any) -> Any:
10161022
"""
10171023
Write data to current file managed by storage.
10181024
data: Any, the data to write, it should be a dataframe, List[dict], etc.

0 commit comments

Comments
 (0)