216 lines
6.3 KiB
Python
216 lines
6.3 KiB
Python
import functools
|
|
import os
|
|
import tempfile
|
|
from contextlib import contextmanager
|
|
from typing import Any, Callable, Iterable, List, Optional
|
|
|
|
import pandas as pd
|
|
|
|
import ray
|
|
from ray.data._internal.execution.interfaces.physical_operator import (
|
|
DataOpTask,
|
|
MetadataOpTask,
|
|
PhysicalOperator,
|
|
RefBundle,
|
|
)
|
|
from ray.data._internal.execution.metadata_fetcher import (
|
|
InlineMetadataFetcher,
|
|
ThreadedMetadataFetcher,
|
|
)
|
|
from ray.data._internal.execution.operators.map_transformer import (
|
|
BlockMapTransformFn,
|
|
MapTransformCallable,
|
|
MapTransformer,
|
|
)
|
|
from ray.data._internal.output_buffer import OutputBlockSizeOption
|
|
from ray.data.block import Block
|
|
from ray.data.expressions import Expr
|
|
|
|
|
|
@ray.remote
|
|
class Counter:
|
|
def __init__(self):
|
|
self.count = 0
|
|
|
|
def increment(self):
|
|
self.count += 1
|
|
|
|
def get(self):
|
|
return self.count
|
|
|
|
def reset(self):
|
|
self.count = 0
|
|
|
|
|
|
@contextmanager
|
|
def gen_bin_files(n):
|
|
with tempfile.TemporaryDirectory() as temp_dir:
|
|
paths = []
|
|
for i in range(n):
|
|
path = os.path.join(temp_dir, f"{i}.bin")
|
|
paths.append(path)
|
|
with open(path, "wb") as fp:
|
|
to_write = str(i) * 500
|
|
fp.write(to_write.encode())
|
|
yield (temp_dir, paths)
|
|
|
|
|
|
def column_udf(col, udf):
|
|
@functools.wraps(udf)
|
|
def wraps(row):
|
|
return {col: udf(row[col])}
|
|
|
|
return wraps
|
|
|
|
|
|
def column_udf_class(col, udf):
|
|
class UDFClass:
|
|
def __call__(self, row):
|
|
return {col: udf(row[col])}
|
|
|
|
return UDFClass
|
|
|
|
|
|
# Ex: named_values("id", [1, 2, 3])
|
|
# Ex: named_values(["id", "id2"], [(1, 1), (2, 2), (3, 3)])
|
|
def named_values(col_names, tuples):
|
|
output = []
|
|
if isinstance(col_names, list):
|
|
for t in tuples:
|
|
output.append(dict(zip(col_names, t)))
|
|
else:
|
|
for t in tuples:
|
|
output.append({col_names: t})
|
|
return output
|
|
|
|
|
|
def extract_values(col_name, tuples):
|
|
return [t[col_name] for t in tuples]
|
|
|
|
|
|
def assert_exprs_equal(actual: List[Expr], expected: List[Expr]):
|
|
"""Assert two expression lists match element-wise.
|
|
|
|
``Expr`` overloads ``==`` to build a comparison expression (e.g.
|
|
``col("a") == 5``), so it can't be used to compare exprs for equality;
|
|
use ``structurally_equals`` instead.
|
|
"""
|
|
actual_names = [e.name for e in actual]
|
|
expected_names = [e.name for e in expected]
|
|
assert len(actual) == len(expected), (actual_names, expected_names)
|
|
assert all(a.structurally_equals(b) for a, b in zip(actual, expected)), (
|
|
actual_names,
|
|
expected_names,
|
|
)
|
|
|
|
|
|
def fetcher_has_pending_work(fetcher: ThreadedMetadataFetcher) -> bool:
|
|
"""Whether a ``ThreadedMetadataFetcher`` still has a submitted pair to emit
|
|
or a postponed done-callback to fire. Test-only poll helper (peeks at the
|
|
fetcher's internals); call from the same thread that drives the fetcher."""
|
|
return any(fetcher._fifos.values()) or bool(fetcher._drained_tasks)
|
|
|
|
|
|
def run_op_tasks_sync(op: PhysicalOperator, only_existing=False):
|
|
"""Run tasks of a PhysicalOperator synchronously.
|
|
|
|
By default, this function will run until the op no longer has any active tasks.
|
|
If only_existing is True, this function will only run the currently existing tasks.
|
|
"""
|
|
tasks = op.get_active_tasks()
|
|
while tasks:
|
|
ref_to_task = {task.get_waitable(): task for task in tasks}
|
|
ready, _ = ray.wait(
|
|
[task.get_waitable() for task in tasks],
|
|
num_returns=len(tasks),
|
|
fetch_local=False,
|
|
timeout=0.1,
|
|
)
|
|
|
|
for ref in ready:
|
|
task = ref_to_task[ref]
|
|
if isinstance(task, DataOpTask):
|
|
# Read all currently available output from the streaming generator
|
|
task.on_data_ready(None, InlineMetadataFetcher())
|
|
# Only remove the task when the generator has been fully exhausted
|
|
if task.has_finished:
|
|
tasks.remove(task)
|
|
else:
|
|
assert isinstance(task, MetadataOpTask)
|
|
task.on_task_finished()
|
|
tasks.remove(task)
|
|
|
|
# NOTE: If only existing tasks need to be handled skip refreshing list
|
|
# of outstanding tasks
|
|
if only_existing:
|
|
pass
|
|
else:
|
|
tasks = op.get_active_tasks()
|
|
|
|
|
|
def run_one_op_task(op):
|
|
"""Run one task of a PhysicalOperator."""
|
|
tasks = op.get_active_tasks()
|
|
|
|
while tasks:
|
|
waitable_to_tasks = {task.get_waitable(): task for task in tasks}
|
|
|
|
# Block, until 1 task is ready
|
|
ready, _ = ray.wait(
|
|
list(waitable_to_tasks.keys()), num_returns=1, fetch_local=False
|
|
)
|
|
|
|
task = waitable_to_tasks[ready[0]]
|
|
# Reset tasks to track just 1 task
|
|
tasks = [task]
|
|
|
|
if isinstance(task, DataOpTask):
|
|
task.on_data_ready(None, InlineMetadataFetcher())
|
|
if task.has_finished:
|
|
tasks.remove(task)
|
|
else:
|
|
assert isinstance(task, MetadataOpTask)
|
|
task.on_task_finished()
|
|
tasks.remove(task)
|
|
|
|
|
|
def _get_blocks(bundle: RefBundle, output_list: List[Block]):
|
|
for block_ref in bundle.block_refs:
|
|
output_list.append(list(ray.get(block_ref)["id"]))
|
|
|
|
|
|
def _mul2_transform(block_iter: Iterable[Block], ctx) -> Iterable[Block]:
|
|
for block in block_iter:
|
|
yield pd.DataFrame({"id": [b * 2 for b in block["id"]]})
|
|
|
|
|
|
def create_map_transformer_from_block_fn(
|
|
block_fn: MapTransformCallable[Block, Block],
|
|
init_fn: Optional[Callable[[], None]] = None,
|
|
output_block_size_option: Optional[OutputBlockSizeOption] = None,
|
|
disable_block_shaping: bool = False,
|
|
):
|
|
"""Create a MapTransformer from a single block-based transform function.
|
|
|
|
This method should only be used for testing and legacy compatibility.
|
|
"""
|
|
return MapTransformer(
|
|
[
|
|
BlockMapTransformFn(
|
|
block_fn,
|
|
output_block_size_option=output_block_size_option,
|
|
disable_block_shaping=disable_block_shaping,
|
|
),
|
|
],
|
|
init_fn=init_fn,
|
|
)
|
|
|
|
|
|
def _take_outputs(op: PhysicalOperator) -> List[Any]:
|
|
output = []
|
|
while op.has_next():
|
|
ref = op.get_next()
|
|
assert ref.owns_blocks, ref
|
|
_get_blocks(ref, output)
|
|
return output
|