From 5ab5ed68ec55399b56d353da3f884ae0be0114c7 Mon Sep 17 00:00:00 2001 From: Xinyuan Lin Date: Sat, 14 Jun 2025 04:46:28 -0700 Subject: [PATCH 01/39] init --- .../architecture/rpc/controlcommands.proto | 24 +++-- .../architecture/rpc/controllerservice.proto | 2 +- .../architecture/rpc/controlreturns.proto | 4 +- .../managers/channel_marker_manager.py | 64 ++++++------ .../core/architecture/managers/context.py | 4 +- .../architecture/packaging/output_manager.py | 8 +- .../sendsemantics/broad_cast_partitioner.py | 6 +- .../hash_based_shuffle_partitioner.py | 6 +- .../sendsemantics/one_to_one_partitioner.py | 6 +- .../architecture/sendsemantics/partitioner.py | 6 +- .../range_based_shuffle_partitioner.py | 6 +- .../sendsemantics/round_robin_partitioner.py | 6 +- .../main/python/core/models/internal_queue.py | 8 +- .../main/python/core/runnables/main_loop.py | 56 +++++------ .../python/core/runnables/network_receiver.py | 10 +- .../python/core/runnables/network_sender.py | 10 +- .../python/core/runnables/test_main_loop.py | 32 +++--- .../core/runnables/test_network_receiver.py | 20 ++-- ...ut_port_materialization_reader_runnable.py | 26 ++--- core/amber/src/main/python/proto/__init__.py | 16 --- .../src/main/python/proto/edu/__init__.py | 16 --- .../src/main/python/proto/edu/uci/__init__.py | 16 --- .../main/python/proto/edu/uci/ics/__init__.py | 16 --- .../proto/edu/uci/ics/amber/__init__.py | 16 --- .../proto/edu/uci/ics/amber/core/__init__.py | 19 +--- .../edu/uci/ics/amber/engine/__init__.py | 16 --- .../ics/amber/engine/architecture/__init__.py | 16 --- .../amber/engine/architecture/rpc/__init__.py | 94 ++++++++---------- .../architecture/sendsemantics/__init__.py | 17 ---- .../engine/architecture/worker/__init__.py | 17 ---- .../uci/ics/amber/engine/common/__init__.py | 17 ---- .../src/main/python/proto/scalapb/__init__.py | 17 ---- .../architecture/controller/Controller.scala | 4 +- ...ControllerAsyncRPCHandlerInitializer.scala | 2 +- ...la => EmbeddedControlMessageHandler.scala} | 18 ++-- .../RetrieveWorkflowStateHandler.scala | 12 +-- .../TakeGlobalCheckpointHandler.scala | 6 +- .../logreplay/EmptyReplayLogger.scala | 4 +- .../logreplay/ReplayLogGenerator.scala | 4 +- .../logreplay/ReplayLogManager.scala | 10 +- .../architecture/logreplay/ReplayLogger.scala | 4 +- .../logreplay/ReplayLoggerImpl.scala | 4 +- .../pythonworker/PythonProxyClient.scala | 14 +-- .../pythonworker/PythonProxyServer.scala | 4 +- .../pythonworker/PythonWorkflowWorker.scala | 8 +- .../WorkerBatchInternalQueue.scala | 4 +- .../worker/ChannelMarkerManager.scala | 99 ------------------- .../engine/architecture/worker/DPThread.scala | 6 +- .../architecture/worker/DataProcessor.scala | 62 ++++-------- .../EmbeddedControlMessageManager.scala | 98 ++++++++++++++++++ .../architecture/worker/PauseType.scala | 4 +- .../architecture/worker/WorkflowWorker.scala | 6 +- ...InputPortMaterializationReaderThread.scala | 24 ++--- .../FinalizeCheckpointHandler.scala | 4 +- .../PrepareCheckpointHandler.scala | 6 +- .../promisehandlers/StartChannelHandler.scala | 2 +- .../engine/common/rpc/AsyncRPCClient.scala | 12 +-- .../rpc/AsyncRPCHandlerInitializer.scala | 10 +- .../workflow/WorkflowExecutionsResource.scala | 2 +- .../web/service/ExecutionRuntimeService.scala | 4 +- .../FriesReconfigurationAlgorithm.scala | 12 +-- .../texera/web/service/WorkflowService.scala | 4 +- .../worker/DataProcessorSpec.scala | 16 +-- .../faulttolerance/CheckpointSpec.scala | 2 +- .../amber/engine/common/virtualidentity.ts | 24 ++--- core/scripts/gui-proto-gen.sh | 2 +- .../edu/uci/ics/amber/core/executor.proto | 1 - .../uci/ics/amber/core/virtualidentity.proto | 2 +- 68 files changed, 424 insertions(+), 673 deletions(-) rename core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/controller/promisehandlers/{ChannelMarkerHandler.scala => EmbeddedControlMessageHandler.scala} (90%) delete mode 100644 core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/ChannelMarkerManager.scala create mode 100644 core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/EmbeddedControlMessageManager.scala diff --git a/core/amber/src/main/protobuf/edu/uci/ics/amber/engine/architecture/rpc/controlcommands.proto b/core/amber/src/main/protobuf/edu/uci/ics/amber/engine/architecture/rpc/controlcommands.proto index ff01ea1515a..8b5f3ccead7 100644 --- a/core/amber/src/main/protobuf/edu/uci/ics/amber/engine/architecture/rpc/controlcommands.proto +++ b/core/amber/src/main/protobuf/edu/uci/ics/amber/engine/architecture/rpc/controlcommands.proto @@ -36,7 +36,7 @@ option (scalapb.options) = { message ControlRequest { oneof sealed_value { // request for controller - PropagateChannelMarkerRequest propagateChannelMarkerRequest = 1; + PropagateEmbeddedControlMessageRequest propagateEmbeddedControlMessageRequest = 1; TakeGlobalCheckpointRequest takeGlobalCheckpointRequest = 2; DebugCommandRequest debugCommandRequest = 3; EvaluatePythonExpressionRequest evaluatePythonExpressionRequest = 4; @@ -88,26 +88,24 @@ message ControlInvocation { int64 commandId = 4; } -// Enum for ChannelMarkerType -enum ChannelMarkerType { +enum EmbeddedControlMessageType { ALL_ALIGNMENT = 0; NO_ALIGNMENT = 1; PORT_ALIGNMENT = 2; } -// Message for ChannelMarkerPayload -message ChannelMarkerPayload { +message EmbeddedControlMessage { option (scalapb.message).extends = "edu.uci.ics.amber.engine.common.ambermessage.WorkflowFIFOMessagePayload"; - core.ChannelMarkerIdentity id = 1 [(scalapb.field).no_box = true]; - ChannelMarkerType markerType = 2; + core.EmbeddedControlMessageIdentity id = 1 [(scalapb.field).no_box = true]; + EmbeddedControlMessageType ecm_type = 2; repeated core.ChannelIdentity scope = 3; map commandMapping = 4; } -message PropagateChannelMarkerRequest { +message PropagateEmbeddedControlMessageRequest { repeated core.PhysicalOpIdentity sourceOpToStartProp = 1; - core.ChannelMarkerIdentity id = 2 [(scalapb.field).no_box = true]; - ChannelMarkerType markerType = 3; + core.EmbeddedControlMessageIdentity id = 2 [(scalapb.field).no_box = true]; + EmbeddedControlMessageType ecm_type = 3; repeated core.PhysicalOpIdentity scope = 4; repeated core.PhysicalOpIdentity targetOps = 5; ControlRequest markerCommand = 6; @@ -116,7 +114,7 @@ message PropagateChannelMarkerRequest { message TakeGlobalCheckpointRequest { bool estimationOnly = 1; - core.ChannelMarkerIdentity checkpointId = 2 [(scalapb.field).no_box = true]; + core.EmbeddedControlMessageIdentity checkpointId = 2 [(scalapb.field).no_box = true]; string destination = 3; } @@ -250,7 +248,7 @@ message AssignPortRequest { } message FinalizeCheckpointRequest { - core.ChannelMarkerIdentity checkpointId = 1 [(scalapb.field).no_box = true]; + core.EmbeddedControlMessageIdentity checkpointId = 1 [(scalapb.field).no_box = true]; string writeTo = 2; } @@ -267,7 +265,7 @@ message UpdateExecutorRequest { } message PrepareCheckpointRequest{ - core.ChannelMarkerIdentity checkpointId = 1 [(scalapb.field).no_box = true]; + core.EmbeddedControlMessageIdentity checkpointId = 1 [(scalapb.field).no_box = true]; bool estimationOnly = 2; } diff --git a/core/amber/src/main/protobuf/edu/uci/ics/amber/engine/architecture/rpc/controllerservice.proto b/core/amber/src/main/protobuf/edu/uci/ics/amber/engine/architecture/rpc/controllerservice.proto index d730afff327..e84e4a7cc4a 100644 --- a/core/amber/src/main/protobuf/edu/uci/ics/amber/engine/architecture/rpc/controllerservice.proto +++ b/core/amber/src/main/protobuf/edu/uci/ics/amber/engine/architecture/rpc/controllerservice.proto @@ -31,7 +31,7 @@ option (scalapb.options) = { service ControllerService { rpc RetrieveWorkflowState(EmptyRequest) returns (RetrieveWorkflowStateResponse); - rpc PropagateChannelMarker(PropagateChannelMarkerRequest) returns (PropagateChannelMarkerResponse); + rpc PropagateEmbeddedControlMessage(PropagateEmbeddedControlMessageRequest) returns (PropagateEmbeddedControlMessageResponse); rpc TakeGlobalCheckpoint(TakeGlobalCheckpointRequest) returns (TakeGlobalCheckpointResponse); rpc DebugCommand(DebugCommandRequest) returns (EmptyReturn); rpc EvaluatePythonExpression(EvaluatePythonExpressionRequest) returns (EvaluatePythonExpressionResponse); diff --git a/core/amber/src/main/protobuf/edu/uci/ics/amber/engine/architecture/rpc/controlreturns.proto b/core/amber/src/main/protobuf/edu/uci/ics/amber/engine/architecture/rpc/controlreturns.proto index 2e6e75bdfad..dc90de5f60b 100644 --- a/core/amber/src/main/protobuf/edu/uci/ics/amber/engine/architecture/rpc/controlreturns.proto +++ b/core/amber/src/main/protobuf/edu/uci/ics/amber/engine/architecture/rpc/controlreturns.proto @@ -34,7 +34,7 @@ message ControlReturn { oneof sealed_value { // controller responses RetrieveWorkflowStateResponse retrieveWorkflowStateResponse = 1; - PropagateChannelMarkerResponse propagateChannelMarkerResponse = 2; + PropagateEmbeddedControlMessageResponse propagateEmbeddedControlMessageResponse = 2; TakeGlobalCheckpointResponse takeGlobalCheckpointResponse = 3; EvaluatePythonExpressionResponse evaluatePythonExpressionResponse = 4; StartWorkflowResponse startWorkflowResponse = 5; @@ -89,7 +89,7 @@ message FinalizeCheckpointResponse { int64 size = 1; } -message PropagateChannelMarkerResponse { +message PropagateEmbeddedControlMessageResponse { map returns = 1; } diff --git a/core/amber/src/main/python/core/architecture/managers/channel_marker_manager.py b/core/amber/src/main/python/core/architecture/managers/channel_marker_manager.py index e551d2b4ce1..387a0dfabaf 100644 --- a/core/amber/src/main/python/core/architecture/managers/channel_marker_manager.py +++ b/core/amber/src/main/python/core/architecture/managers/channel_marker_manager.py @@ -21,70 +21,66 @@ from core.architecture.packaging.input_manager import Channel from proto.edu.uci.ics.amber.core import ActorVirtualIdentity, ChannelIdentity from proto.edu.uci.ics.amber.engine.architecture.rpc import ( - ChannelMarkerPayload, - ChannelMarkerType, + EmbeddedControlMessage, + EmbeddedControlMessageType, ) -class ChannelMarkerManager: +class EmbeddedControlMessageManager: def __init__(self, actor_id: ActorVirtualIdentity, input_gateway): self.actor_id = actor_id self.input_gateway = input_gateway - self.marker_received: Dict[str, Set[ChannelIdentity]] = defaultdict(set) + self.ecm_received: Dict[str, Set[ChannelIdentity]] = defaultdict(set) - def is_marker_aligned( - self, from_channel: ChannelIdentity, marker: ChannelMarkerPayload + def ecm_aligned( + self, from_channel: ChannelIdentity, ecm: EmbeddedControlMessage ) -> bool: """ - Checks whether a channel marker has been received from all expected + Checks whether a ECM has been received from all expected input channels, determining whether further processing can proceed. Args: - from_channel (ChannelIdentity): The channel from which the marker - was received. - marker (ChannelMarkerPayload): The marker payload containing its - type and scope. + from_channel (ChannelIdentity): The channel from which the ECM was received. + ecm (EmbeddedControlMessage): The ECM payload containing its type and scope. Returns: - bool: True if the marker is considered aligned and processing can + bool: True if the ECM is considered aligned and processing can continue, False otherwise. """ - marker_id = marker.id - self.marker_received[marker_id].add(from_channel) - marker_received_from_all_channels = self.get_channels_within_scope( - marker - ).issubset(self.marker_received[marker_id]) + self.ecm_received[ecm.id].add(from_channel) + ecm_received_from_all_channels = self.get_channels_within_scope( + ecm + ).issubset(self.ecm_received[ecm.id]) - if marker.marker_type == ChannelMarkerType.ALL_ALIGNMENT: - epoch_marker_completed = marker_received_from_all_channels - elif marker.marker_type == ChannelMarkerType.PORT_ALIGNMENT: + if ecm.ecm_type == EmbeddedControlMessageType.ALL_ALIGNMENT: + ecm_completed = ecm_received_from_all_channels + elif ecm.ecm_type == EmbeddedControlMessageType.PORT_ALIGNMENT: port_id = self.input_gateway.get_port_id(from_channel) - marker_received_from_current_port = ( + ecm_completed = ( self.input_gateway.get_port(port_id) .get_channels() - .issubset(self.marker_received[marker_id]) + .issubset(self.ecm_received[ecm.id]) ) - epoch_marker_completed = marker_received_from_current_port - elif marker.marker_type == ChannelMarkerType.NO_ALIGNMENT: - epoch_marker_completed = ( - len(self.marker_received[marker_id]) == 1 - ) # Only the first marker triggers + elif ecm.ecm_type == EmbeddedControlMessageType.NO_ALIGNMENT: + ecm_completed = ( + len(self.ecm_received[ecm.id]) == 1 + ) # Only the first ECM triggers else: - raise ValueError(f"Unsupported marker type: {marker.marker_type}") + raise ValueError(f"Unsupported ECM type: {ecm.ecm_type}") - if marker_received_from_all_channels: - del self.marker_received[marker_id] # Clean up if all markers are received + if ecm_received_from_all_channels: + del self.ecm_received[ecm.id] # Clean up if all ECMs are received - return epoch_marker_completed + return ecm_completed def get_channels_within_scope( - self, marker: ChannelMarkerPayload + self, ecm: EmbeddedControlMessage ) -> Dict["ChannelIdentity", "Channel"].keys: - if marker.scope: + if ecm.scope: upstreams = { channel_id - for channel_id in marker.scope + for channel_id in ecm.scope if channel_id.to_worker_id == self.actor_id } return self.input_gateway.get_all_channel_ids() & upstreams diff --git a/core/amber/src/main/python/core/architecture/managers/context.py b/core/amber/src/main/python/core/architecture/managers/context.py index fc957033e7f..964d5fccebe 100644 --- a/core/amber/src/main/python/core/architecture/managers/context.py +++ b/core/amber/src/main/python/core/architecture/managers/context.py @@ -19,7 +19,7 @@ from proto.edu.uci.ics.amber.engine.architecture.worker import WorkerState from typing import Optional from .console_message_manager import ConsoleMessageManager -from .channel_marker_manager import ChannelMarkerManager +from .channel_marker_manager import EmbeddedControlMessageManager from .debug_manager import DebugManager from .exception_manager import ExceptionManager from .state_processing_manager import StateProcessingManager @@ -67,7 +67,7 @@ def __init__(self, worker_id, input_queue): ) self.output_manager = OutputManager(worker_id) self.input_manager = InputManager(worker_id, self.input_queue) - self.channel_marker_manager = ChannelMarkerManager( + self.ecm_manager = EmbeddedControlMessageManager( ActorVirtualIdentity(worker_id), self.input_manager ) self.console_message_manager = ConsoleMessageManager() diff --git a/core/amber/src/main/python/core/architecture/packaging/output_manager.py b/core/amber/src/main/python/core/architecture/packaging/output_manager.py index 9808a3ba83e..16a2c264d2e 100644 --- a/core/amber/src/main/python/core/architecture/packaging/output_manager.py +++ b/core/amber/src/main/python/core/architecture/packaging/output_manager.py @@ -57,7 +57,7 @@ PortIdentity, ChannelIdentity, ) -from proto.edu.uci.ics.amber.engine.architecture.rpc import ChannelMarkerPayload +from proto.edu.uci.ics.amber.engine.architecture.rpc import EmbeddedControlMessage from proto.edu.uci.ics.amber.engine.architecture.sendsemantics import ( HashBasedShufflePartitioning, OneToOnePartitioning, @@ -223,14 +223,14 @@ def tuple_to_batch( ) def emit_channel_marker( - self, to: ActorVirtualIdentity, marker: ChannelMarkerPayload - ) -> Iterable[Union[DataPayload, ChannelMarkerPayload]]: + self, to: ActorVirtualIdentity, marker: EmbeddedControlMessage + ) -> Iterable[Union[DataPayload, EmbeddedControlMessage]]: return chain( *( ( ( payload - if isinstance(payload, ChannelMarkerPayload) + if isinstance(payload, EmbeddedControlMessage) else self.tuple_to_frame(payload) ) for payload in partitioner.flush(to, marker) diff --git a/core/amber/src/main/python/core/architecture/sendsemantics/broad_cast_partitioner.py b/core/amber/src/main/python/core/architecture/sendsemantics/broad_cast_partitioner.py index 9ed9e2eb656..eeb1ca43ef3 100644 --- a/core/amber/src/main/python/core/architecture/sendsemantics/broad_cast_partitioner.py +++ b/core/amber/src/main/python/core/architecture/sendsemantics/broad_cast_partitioner.py @@ -27,7 +27,7 @@ BroadcastPartitioning, ) from proto.edu.uci.ics.amber.core import ActorVirtualIdentity -from proto.edu.uci.ics.amber.engine.architecture.rpc import ChannelMarkerPayload +from proto.edu.uci.ics.amber.engine.architecture.rpc import EmbeddedControlMessage class BroadcastPartitioner(Partitioner): @@ -51,8 +51,8 @@ def add_tuple_to_batch( @overrides def flush( - self, to: ActorVirtualIdentity, marker: ChannelMarkerPayload - ) -> Iterator[typing.Union[ChannelMarkerPayload, typing.List[Tuple]]]: + self, to: ActorVirtualIdentity, marker: EmbeddedControlMessage + ) -> Iterator[typing.Union[EmbeddedControlMessage, typing.List[Tuple]]]: if len(self.batch) > 0: for receiver in self.receivers: if receiver == to: diff --git a/core/amber/src/main/python/core/architecture/sendsemantics/hash_based_shuffle_partitioner.py b/core/amber/src/main/python/core/architecture/sendsemantics/hash_based_shuffle_partitioner.py index 6936db80da2..7ea045da52e 100644 --- a/core/amber/src/main/python/core/architecture/sendsemantics/hash_based_shuffle_partitioner.py +++ b/core/amber/src/main/python/core/architecture/sendsemantics/hash_based_shuffle_partitioner.py @@ -29,7 +29,7 @@ Partitioning, ) from proto.edu.uci.ics.amber.core import ActorVirtualIdentity -from proto.edu.uci.ics.amber.engine.architecture.rpc import ChannelMarkerPayload +from proto.edu.uci.ics.amber.engine.architecture.rpc import EmbeddedControlMessage class HashBasedShufflePartitioner(Partitioner): @@ -61,8 +61,8 @@ def add_tuple_to_batch( @overrides def flush( - self, to: ActorVirtualIdentity, marker: ChannelMarkerPayload - ) -> Iterator[typing.Union[ChannelMarkerPayload, typing.List[Tuple]]]: + self, to: ActorVirtualIdentity, marker: EmbeddedControlMessage + ) -> Iterator[typing.Union[EmbeddedControlMessage, typing.List[Tuple]]]: for receiver, batch in self.receivers: if receiver == to: if len(batch) > 0: diff --git a/core/amber/src/main/python/core/architecture/sendsemantics/one_to_one_partitioner.py b/core/amber/src/main/python/core/architecture/sendsemantics/one_to_one_partitioner.py index 08e32d36e8d..a259d7c0aff 100644 --- a/core/amber/src/main/python/core/architecture/sendsemantics/one_to_one_partitioner.py +++ b/core/amber/src/main/python/core/architecture/sendsemantics/one_to_one_partitioner.py @@ -28,7 +28,7 @@ Partitioning, ) from proto.edu.uci.ics.amber.core import ActorVirtualIdentity -from proto.edu.uci.ics.amber.engine.architecture.rpc import ChannelMarkerPayload +from proto.edu.uci.ics.amber.engine.architecture.rpc import EmbeddedControlMessage class OneToOnePartitioner(Partitioner): @@ -52,8 +52,8 @@ def add_tuple_to_batch( @overrides def flush( - self, to: ActorVirtualIdentity, marker: ChannelMarkerPayload - ) -> Iterator[typing.Union[ChannelMarkerPayload, typing.List[Tuple]]]: + self, to: ActorVirtualIdentity, marker: EmbeddedControlMessage + ) -> Iterator[typing.Union[EmbeddedControlMessage, typing.List[Tuple]]]: if len(self.batch) > 0: yield self.batch self.reset() diff --git a/core/amber/src/main/python/core/architecture/sendsemantics/partitioner.py b/core/amber/src/main/python/core/architecture/sendsemantics/partitioner.py index 1a0361d361c..343c06080a2 100644 --- a/core/amber/src/main/python/core/architecture/sendsemantics/partitioner.py +++ b/core/amber/src/main/python/core/architecture/sendsemantics/partitioner.py @@ -24,7 +24,7 @@ from core.util import get_one_of from proto.edu.uci.ics.amber.engine.architecture.sendsemantics import Partitioning from proto.edu.uci.ics.amber.core import ActorVirtualIdentity -from proto.edu.uci.ics.amber.engine.architecture.rpc import ChannelMarkerPayload +from proto.edu.uci.ics.amber.engine.architecture.rpc import EmbeddedControlMessage class Partitioner(ABC): @@ -37,8 +37,8 @@ def add_tuple_to_batch( pass def flush( - self, to: ActorVirtualIdentity, marker: ChannelMarkerPayload - ) -> Iterator[typing.Union[ChannelMarkerPayload, typing.List[Tuple]]]: + self, to: ActorVirtualIdentity, marker: EmbeddedControlMessage + ) -> Iterator[typing.Union[EmbeddedControlMessage, typing.List[Tuple]]]: pass def flush_state( diff --git a/core/amber/src/main/python/core/architecture/sendsemantics/range_based_shuffle_partitioner.py b/core/amber/src/main/python/core/architecture/sendsemantics/range_based_shuffle_partitioner.py index 5b27d994178..4cf2f0b8922 100644 --- a/core/amber/src/main/python/core/architecture/sendsemantics/range_based_shuffle_partitioner.py +++ b/core/amber/src/main/python/core/architecture/sendsemantics/range_based_shuffle_partitioner.py @@ -30,7 +30,7 @@ Partitioning, ) from proto.edu.uci.ics.amber.core import ActorVirtualIdentity -from proto.edu.uci.ics.amber.engine.architecture.rpc import ChannelMarkerPayload +from proto.edu.uci.ics.amber.engine.architecture.rpc import EmbeddedControlMessage class RangeBasedShufflePartitioner(Partitioner): @@ -75,8 +75,8 @@ def add_tuple_to_batch( @overrides def flush( - self, to: ActorVirtualIdentity, marker: ChannelMarkerPayload - ) -> Iterator[typing.Union[ChannelMarkerPayload, typing.List[Tuple]]]: + self, to: ActorVirtualIdentity, marker: EmbeddedControlMessage + ) -> Iterator[typing.Union[EmbeddedControlMessage, typing.List[Tuple]]]: for receiver, batch in self.receivers: if receiver == to: if len(batch) > 0: diff --git a/core/amber/src/main/python/core/architecture/sendsemantics/round_robin_partitioner.py b/core/amber/src/main/python/core/architecture/sendsemantics/round_robin_partitioner.py index 9a05a7cb9fc..725095539f4 100644 --- a/core/amber/src/main/python/core/architecture/sendsemantics/round_robin_partitioner.py +++ b/core/amber/src/main/python/core/architecture/sendsemantics/round_robin_partitioner.py @@ -27,7 +27,7 @@ RoundRobinPartitioning, ) from proto.edu.uci.ics.amber.core import ActorVirtualIdentity -from proto.edu.uci.ics.amber.engine.architecture.rpc import ChannelMarkerPayload +from proto.edu.uci.ics.amber.engine.architecture.rpc import EmbeddedControlMessage class RoundRobinPartitioner(Partitioner): @@ -62,8 +62,8 @@ def add_tuple_to_batch( @overrides def flush( - self, to: ActorVirtualIdentity, marker: ChannelMarkerPayload - ) -> Iterator[typing.Union[ChannelMarkerPayload, typing.List[Tuple]]]: + self, to: ActorVirtualIdentity, marker: EmbeddedControlMessage + ) -> Iterator[typing.Union[EmbeddedControlMessage, typing.List[Tuple]]]: for receiver, batch in self.receivers: if receiver == to: if len(batch) > 0: diff --git a/core/amber/src/main/python/core/models/internal_queue.py b/core/amber/src/main/python/core/models/internal_queue.py index cb73fda2706..8debc362522 100644 --- a/core/amber/src/main/python/core/models/internal_queue.py +++ b/core/amber/src/main/python/core/models/internal_queue.py @@ -29,7 +29,7 @@ ) from core.util.customized_queue.queue_base import IQueue, QueueElement from proto.edu.uci.ics.amber.core import ChannelIdentity -from proto.edu.uci.ics.amber.engine.architecture.rpc import ChannelMarkerPayload +from proto.edu.uci.ics.amber.engine.architecture.rpc import EmbeddedControlMessage from proto.edu.uci.ics.amber.engine.common import ControlPayloadV2 @@ -49,8 +49,8 @@ class ControlElement(InternalQueueElement): @dataclass -class ChannelMarkerElement(InternalQueueElement): - payload: ChannelMarkerPayload +class EmbeddedControlMessageElement(InternalQueueElement): + payload: EmbeddedControlMessage T = TypeVar("T", bound=InternalQueueElement) @@ -80,7 +80,7 @@ def put(self, item: T) -> None: if item.tag not in self._queue_ids: self._queue.add_sub_queue(item.tag, 1 if item.tag.is_control else 2) self._queue_ids.add(item.tag) - if isinstance(item, (DataElement, InternalMarker, ChannelMarkerElement)): + if isinstance(item, (DataElement, InternalMarker, EmbeddedControlMessageElement)): self._queue.put(item.tag, item) elif isinstance(item, ControlElement): self._queue.put(item.tag, item) diff --git a/core/amber/src/main/python/core/runnables/main_loop.py b/core/amber/src/main/python/core/runnables/main_loop.py index f1e03981297..88767faaac5 100644 --- a/core/amber/src/main/python/core/runnables/main_loop.py +++ b/core/amber/src/main/python/core/runnables/main_loop.py @@ -35,7 +35,7 @@ from core.models.internal_queue import ( DataElement, ControlElement, - ChannelMarkerElement, + EmbeddedControlMessageElement, InternalQueueElement, ) from core.models.state import State @@ -51,8 +51,8 @@ PortCompletedRequest, EmptyRequest, ConsoleMessageTriggeredRequest, - ChannelMarkerType, - ChannelMarkerPayload, + EmbeddedControlMessageType, + EmbeddedControlMessagePayload, AsyncRpcContext, ControlRequest, ) @@ -64,7 +64,7 @@ ActorVirtualIdentity, PortIdentity, ChannelIdentity, - ChannelMarkerIdentity, + EmbeddedControlMessageIdentity, ) @@ -146,8 +146,8 @@ def receive(self, next_entry: QueueElement) -> None: self._process_data_element, ControlElement, self._process_control_element, - ChannelMarkerElement, - self._process_channel_marker_payload, + EmbeddedControlMessageElement, + self._process_ecm, ) def process_control_payload( @@ -259,7 +259,7 @@ def _process_state(self, state_: State) -> None: def _process_start_channel(self) -> None: self._send_channel_marker_to_data_channels( - "StartChannel", ChannelMarkerType.NO_ALIGNMENT + "StartChannel", EmbeddedControlMessageType.NO_ALIGNMENT ) self.process_input_state() @@ -287,7 +287,7 @@ def _process_end_channel(self) -> None: self.context.output_manager.close_port_storage_writers() self._send_channel_marker_to_data_channels( - "EndChannel", ChannelMarkerType.PORT_ALIGNMENT + "EndChannel", EmbeddedControlMessageType.PORT_ALIGNMENT ) # Need to send port completed even if there is no downstream link @@ -297,34 +297,32 @@ def _process_end_channel(self) -> None: ) self.complete() - def _process_channel_marker_payload(self, marker_elem: ChannelMarkerElement): + def _process_ecm(self, ecm_element: EmbeddedControlMessageElement): """ Processes a received channel marker payload and handles synchronization, command execution, and forwarding to downstream channels if applicable. Args: - marker_elem (ChannelMarkerElement): The received channel marker - element. + ecm_element (EmbeddedControlMessageElement): The received channel marker element. """ - marker_payload = marker_elem.payload - marker_id = marker_payload.id - command = marker_payload.command_mapping.get(self.context.worker_id) + ecm = ecm_element.payload + command = ecm.command_mapping.get(self.context.worker_id) channel_id = self.context.current_input_channel_id logger.info( f"receive channel marker from {channel_id}," - f" id = {marker_id}, cmd = {command}" + f" id = {ecm.id}, cmd = {command}" ) - if marker_payload.marker_type != ChannelMarkerType.NO_ALIGNMENT: + if ecm.ecm_type != EmbeddedControlMessageType.NO_ALIGNMENT: self.context.pause_manager.pause_input_channel( PauseType.MARKER_PAUSE, channel_id ) - if self.context.channel_marker_manager.is_marker_aligned( - channel_id, marker_payload + if self.context.ecm_manager.ecm_aligned( + channel_id, ecm ): logger.info( f"process channel marker from {channel_id}," - f" id = {marker_id}, cmd = {command}" + f" id = {ecm.id}, cmd = {command}" ) if command is not None: @@ -332,7 +330,7 @@ def _process_channel_marker_payload(self, marker_elem: ChannelMarkerElement): downstream_channels_in_scope = { scope - for scope in marker_payload.scope + for scope in ecm.scope if scope.from_worker_id == ActorVirtualIdentity(self.context.worker_id) } if downstream_channels_in_scope: @@ -342,11 +340,11 @@ def _process_channel_marker_payload(self, marker_elem: ChannelMarkerElement): if active_channel_id in downstream_channels_in_scope: logger.info( f"send marker to {active_channel_id}," - f" id = {marker_id}, cmd = {command}" + f" id = {ecm.id}, cmd = {command}" ) - self._send_channel_marker(active_channel_id, marker_payload) + self._send_channel_marker(active_channel_id, ecm) - if marker_payload.marker_type != ChannelMarkerType.NO_ALIGNMENT: + if ecm.ecm_type != EmbeddedControlMessageType.NO_ALIGNMENT: self.context.pause_manager.resume(PauseType.MARKER_PAUSE) if self.context.tuple_processing_manager.current_internal_marker: @@ -356,12 +354,12 @@ def _process_channel_marker_payload(self, marker_elem: ChannelMarkerElement): }[type(self.context.tuple_processing_manager.current_internal_marker)]() def _send_channel_marker_to_data_channels( - self, method_name: str, alignment: ChannelMarkerType + self, method_name: str, alignment: EmbeddedControlMessageType ) -> None: for active_channel_id in self.context.output_manager.get_output_channel_ids(): if not active_channel_id.is_control: - marker_payload = ChannelMarkerPayload( - ChannelMarkerIdentity(method_name), + marker_payload = EmbeddedControlMessagePayload( + EmbeddedControlMessageIdentity(method_name), alignment, [], { @@ -378,15 +376,15 @@ def _send_channel_marker_to_data_channels( self._send_channel_marker(active_channel_id, marker_payload) def _send_channel_marker( - self, channel_id: ChannelIdentity, marker_payload: ChannelMarkerPayload + self, channel_id: ChannelIdentity, marker_payload: EmbeddedControlMessage ) -> None: for batch in self.context.output_manager.emit_channel_marker( channel_id.to_worker_id, marker_payload ): tag = channel_id element = ( - ChannelMarkerElement(tag=tag, payload=batch) - if isinstance(batch, ChannelMarkerPayload) + EmbeddedControlMessageElement(tag=tag, payload=batch) + if isinstance(batch, EmbeddedControlMessage) else DataElement(tag=tag, payload=batch) ) self._output_queue.put(element) diff --git a/core/amber/src/main/python/core/runnables/network_receiver.py b/core/amber/src/main/python/core/runnables/network_receiver.py index 3fcb091a2b8..474d3386876 100644 --- a/core/amber/src/main/python/core/runnables/network_receiver.py +++ b/core/amber/src/main/python/core/runnables/network_receiver.py @@ -38,13 +38,13 @@ DataElement, ControlElement, InternalQueue, - ChannelMarkerElement, + EmbeddedControlMessageElement, ) from core.models.state import State from core.proxy import ProxyServer from core.util import Stoppable, get_one_of from core.util.runnable.runnable import Runnable -from proto.edu.uci.ics.amber.engine.architecture.rpc import ChannelMarkerPayload +from proto.edu.uci.ics.amber.engine.architecture.rpc import EmbeddedControlMessage from proto.edu.uci.ics.amber.engine.common import ( PythonControlMessage, PythonDataHeader, @@ -98,14 +98,14 @@ def data_handler(command: bytes, table: Table) -> int: "State", lambda _: StateFrame(State(table)), "ChannelMarker", - lambda _: ChannelMarkerPayload().parse(table["payload"][0].as_py()), + lambda _: EmbeddedControlMessage().parse(table["payload"][0].as_py()), ) - if isinstance(payload, ChannelMarkerPayload): + if isinstance(payload, EmbeddedControlMessage): for channel_id in payload.scope: if not channel_id.is_control: channel_id.is_control = False shared_queue.put( - ChannelMarkerElement(tag=data_header.tag, payload=payload) + EmbeddedControlMessageElement(tag=data_header.tag, payload=payload) ) else: shared_queue.put(DataElement(tag=data_header.tag, payload=payload)) diff --git a/core/amber/src/main/python/core/runnables/network_sender.py b/core/amber/src/main/python/core/runnables/network_sender.py index d1afde90cbb..ca536bc5388 100644 --- a/core/amber/src/main/python/core/runnables/network_sender.py +++ b/core/amber/src/main/python/core/runnables/network_sender.py @@ -26,11 +26,11 @@ InternalQueueElement, DataElement, ControlElement, - ChannelMarkerElement, + EmbeddedControlMessageElement, ) from core.proxy import ProxyClient from core.util import StoppableQueueBlockingRunnable -from proto.edu.uci.ics.amber.engine.architecture.rpc import ChannelMarkerPayload +from proto.edu.uci.ics.amber.engine.architecture.rpc import EmbeddedControlMessage from proto.edu.uci.ics.amber.engine.common import ( ControlPayloadV2, PythonControlMessage, @@ -62,21 +62,21 @@ def receive(self, next_entry: InternalQueueElement): self._send_data(next_entry.tag, next_entry.payload) elif isinstance(next_entry, ControlElement): self._send_control(next_entry.tag, next_entry.payload) - elif isinstance(next_entry, ChannelMarkerElement): + elif isinstance(next_entry, EmbeddedControlMessageElement): self._send_channel_marker(next_entry.tag, next_entry.payload) else: raise TypeError(f"Unexpected entry {next_entry}") @logger.catch(reraise=True) def _send_channel_marker( - self, to: ChannelIdentity, data_payload: ChannelMarkerPayload + self, to: ChannelIdentity, data_payload: EmbeddedControlMessage ) -> None: """ Sends a channel marker payload to the specified channel. Args: to (ChannelIdentity): The target channel to which the marker should be sent. - data_payload (ChannelMarkerPayload): The channel marker payload to send. + data_payload (EmbeddedControlMessage): The channel marker payload to send. This function constructs a `PythonDataHeader` with the appropriate metadata, serializes the payload into an Arrow table, and sends it using the proxy client. diff --git a/core/amber/src/main/python/core/runnables/test_main_loop.py b/core/amber/src/main/python/core/runnables/test_main_loop.py index a7a5646004e..973541a78a3 100644 --- a/core/amber/src/main/python/core/runnables/test_main_loop.py +++ b/core/amber/src/main/python/core/runnables/test_main_loop.py @@ -28,7 +28,7 @@ InternalQueue, Tuple, ) -from core.models.internal_queue import DataElement, ControlElement, ChannelMarkerElement +from core.models.internal_queue import DataElement, ControlElement, EmbeddedControlMessageElement from core.runnables import MainLoop from core.util import set_one_of from proto.edu.uci.ics.amber.core import ( @@ -40,7 +40,7 @@ PortIdentity, OpExecWithCode, OpExecInitInfo, - ChannelMarkerIdentity, + EmbeddedControlMessageIdentity, ) from proto.edu.uci.ics.amber.engine.architecture.rpc import ( ControlRequest, @@ -57,8 +57,8 @@ PortCompletedRequest, AsyncRpcContext, WorkerStateResponse, - ChannelMarkerType, - ChannelMarkerPayload, + EmbeddedControlMessageType, + EmbeddedControlMessage, ) from proto.edu.uci.ics.amber.engine.architecture.sendsemantics import ( OneToOnePartitioning, @@ -187,11 +187,11 @@ def mock_batch_data_elements(self, mock_batch, mock_data_input_channel): @pytest.fixture def mock_end_of_upstream(self, mock_tuple, mock_data_input_channel): - return ChannelMarkerElement( + return EmbeddedControlMessageElement( tag=mock_data_input_channel, - payload=ChannelMarkerPayload( - ChannelMarkerIdentity("EndChannel"), - ChannelMarkerType.PORT_ALIGNMENT, + payload=EmbeddedControlMessage( + EmbeddedControlMessageIdentity("EndChannel"), + EmbeddedControlMessageType.PORT_ALIGNMENT, [], { mock_data_input_channel.to_worker_id.name: ControlInvocation( @@ -709,11 +709,11 @@ def test_main_loop_thread_can_process_messages( ) output_queue.enable_data(InternalQueue.DisableType.DISABLE_BY_PAUSE) - assert output_queue.get() == ChannelMarkerElement( + assert output_queue.get() == EmbeddedControlMessageElement( tag=mock_data_output_channel, - payload=ChannelMarkerPayload( - ChannelMarkerIdentity("EndChannel"), - ChannelMarkerType.PORT_ALIGNMENT, + payload=EmbeddedControlMessage( + EmbeddedControlMessageIdentity("EndChannel"), + EmbeddedControlMessageType.PORT_ALIGNMENT, [], { mock_data_output_channel.to_worker_id.name: ControlInvocation( @@ -1165,15 +1165,15 @@ def test_main_loop_thread_can_align_ecm( "NoOperation", EmptyRequest(), AsyncRpcContext(), 98 ) } - test_marker = ChannelMarkerPayload( - "test_marker", ChannelMarkerType.ALL_ALIGNMENT, scope, command_mapping + test_marker = EmbeddedControlMessage( + "test_marker", EmbeddedControlMessageType.ALL_ALIGNMENT, scope, command_mapping ) input_queue.put( - ChannelMarkerElement(tag=mock_control_input_channel, payload=test_marker) + EmbeddedControlMessageElement(tag=mock_control_input_channel, payload=test_marker) ) input_queue.put(mock_binary_data_element) input_queue.put( - ChannelMarkerElement(tag=mock_data_input_channel, payload=test_marker) + EmbeddedControlMessageElement(tag=mock_data_input_channel, payload=test_marker) ) output_data_element: DataElement = output_queue.get() assert output_data_element.tag == mock_data_output_channel diff --git a/core/amber/src/main/python/core/runnables/test_network_receiver.py b/core/amber/src/main/python/core/runnables/test_network_receiver.py index a3f4e620d38..8920b9c4731 100644 --- a/core/amber/src/main/python/core/runnables/test_network_receiver.py +++ b/core/amber/src/main/python/core/runnables/test_network_receiver.py @@ -24,7 +24,7 @@ InternalQueue, ControlElement, DataElement, - ChannelMarkerElement, + EmbeddedControlMessageElement, ) from core.models.payload import DataFrame from core.proxy import ProxyClient @@ -33,8 +33,8 @@ from core.util.proto import set_one_of from proto.edu.uci.ics.amber.engine.architecture.rpc import ( ControlInvocation, - ChannelMarkerPayload, - ChannelMarkerType, + EmbeddedControlMessage, + EmbeddedControlMessageType, EmptyRequest, AsyncRpcContext, ControlRequest, @@ -43,7 +43,7 @@ from proto.edu.uci.ics.amber.core import ( ActorVirtualIdentity, ChannelIdentity, - ChannelMarkerIdentity, + EmbeddedControlMessageIdentity, ) @@ -169,7 +169,7 @@ def test_network_receiver_can_receive_channel_marker( network_sender_thread.start() worker_id = ActorVirtualIdentity(name="test") channel_id = ChannelIdentity(worker_id, worker_id, False) - marker_id = ChannelMarkerIdentity("test_marker") + marker_id = EmbeddedControlMessageIdentity("test_marker") scope = [channel_id] rpc_context = AsyncRpcContext(worker_id, worker_id) command_mapping = { @@ -181,19 +181,19 @@ def test_network_receiver_can_receive_channel_marker( ) } input_queue.put( - ChannelMarkerElement( + EmbeddedControlMessageElement( tag=channel_id, - payload=ChannelMarkerPayload( + payload=EmbeddedControlMessage( marker_id, - ChannelMarkerType.ALL_ALIGNMENT, + EmbeddedControlMessageType.ALL_ALIGNMENT, scope, command_mapping, ), ) ) element: DataElement = output_queue.get() - assert isinstance(element.payload, ChannelMarkerPayload) - assert element.payload.marker_type == ChannelMarkerType.ALL_ALIGNMENT + assert isinstance(element.payload, EmbeddedControlMessage) + assert element.payload.ecm_type == EmbeddedControlMessageType.ALL_ALIGNMENT assert element.payload.id == marker_id assert element.payload.command_mapping == command_mapping assert element.payload.scope == scope diff --git a/core/amber/src/main/python/core/storage/runnables/input_port_materialization_reader_runnable.py b/core/amber/src/main/python/core/storage/runnables/input_port_materialization_reader_runnable.py index f1cf0f77d27..056338e7245 100644 --- a/core/amber/src/main/python/core/storage/runnables/input_port_materialization_reader_runnable.py +++ b/core/amber/src/main/python/core/storage/runnables/input_port_materialization_reader_runnable.py @@ -34,7 +34,7 @@ RoundRobinPartitioner, ) from core.models import Tuple, InternalQueue, DataFrame, DataPayload -from core.models.internal_queue import DataElement, ChannelMarkerElement +from core.models.internal_queue import DataElement, EmbeddedControlMessageElement from core.storage.document_factory import DocumentFactory from core.util import Stoppable, get_one_of from core.util.runnable.runnable import Runnable @@ -42,7 +42,7 @@ from proto.edu.uci.ics.amber.core import ( ActorVirtualIdentity, ChannelIdentity, - ChannelMarkerIdentity, + EmbeddedControlMessageIdentity, ) from proto.edu.uci.ics.amber.engine.architecture.sendsemantics import ( HashBasedShufflePartitioning, @@ -57,8 +57,8 @@ from proto.edu.uci.ics.amber.engine.architecture.rpc import ( ControlInvocation, EmptyRequest, - ChannelMarkerType, - ChannelMarkerPayload, + EmbeddedControlMessageType, + EmbeddedControlMessage, AsyncRpcContext, ControlRequest, ) @@ -131,7 +131,7 @@ def run(self) -> None: self.materialization, self.tuple_schema = DocumentFactory.open_document( self.uri ) - self.emit_channel_marker("StartChannel", ChannelMarkerType.NO_ALIGNMENT) + self.emit_channel_marker("StartChannel", EmbeddedControlMessageType.NO_ALIGNMENT) storage_iterator = self.materialization.get() # Iterate and process tuples. @@ -142,7 +142,7 @@ def run(self) -> None: # a batch-based iterator. for data_frame in self.tuple_to_batch_with_filter(tup): self.emit_payload(data_frame) - self.emit_channel_marker("EndChannel", ChannelMarkerType.PORT_ALIGNMENT) + self.emit_channel_marker("EndChannel", EmbeddedControlMessageType.PORT_ALIGNMENT) except Exception as err: logger.exception(err) @@ -151,15 +151,15 @@ def stop(self): self._stopped = True def emit_channel_marker( - self, method_name: str, alignment: ChannelMarkerType + self, method_name: str, alignment: EmbeddedControlMessageType ) -> None: """ Emit a channel marker (StartChannel or EndChannel), and flush the remaining data batches if any. This mimics the iterator logic of that in output manager. """ - marker_payload = ChannelMarkerPayload( - ChannelMarkerIdentity(method_name), + marker_payload = EmbeddedControlMessage( + EmbeddedControlMessageIdentity(method_name), alignment, [], { @@ -175,18 +175,18 @@ def emit_channel_marker( for payload in self.partitioner.flush(self.worker_actor_id, marker_payload): final_payload = ( payload - if isinstance(payload, ChannelMarkerPayload) + if isinstance(payload, EmbeddedControlMessage) else self.tuples_to_data_frame(payload) ) self.emit_payload(final_payload) - def emit_payload(self, payload: Union[DataPayload, ChannelMarkerPayload]) -> None: + def emit_payload(self, payload: Union[DataPayload, EmbeddedControlMessage]) -> None: """ Put the payload to the DP internal queue. """ queue_element = ( - ChannelMarkerElement(tag=self.channel_id, payload=payload) - if isinstance(payload, ChannelMarkerPayload) + EmbeddedControlMessageElement(tag=self.channel_id, payload=payload) + if isinstance(payload, EmbeddedControlMessage) else DataElement(tag=self.channel_id, payload=payload) ) self.queue.put(queue_element) diff --git a/core/amber/src/main/python/proto/__init__.py b/core/amber/src/main/python/proto/__init__.py index 13a83393a91..e69de29bb2d 100644 --- a/core/amber/src/main/python/proto/__init__.py +++ b/core/amber/src/main/python/proto/__init__.py @@ -1,16 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. diff --git a/core/amber/src/main/python/proto/edu/__init__.py b/core/amber/src/main/python/proto/edu/__init__.py index 13a83393a91..e69de29bb2d 100644 --- a/core/amber/src/main/python/proto/edu/__init__.py +++ b/core/amber/src/main/python/proto/edu/__init__.py @@ -1,16 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. diff --git a/core/amber/src/main/python/proto/edu/uci/__init__.py b/core/amber/src/main/python/proto/edu/uci/__init__.py index 13a83393a91..e69de29bb2d 100644 --- a/core/amber/src/main/python/proto/edu/uci/__init__.py +++ b/core/amber/src/main/python/proto/edu/uci/__init__.py @@ -1,16 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. diff --git a/core/amber/src/main/python/proto/edu/uci/ics/__init__.py b/core/amber/src/main/python/proto/edu/uci/ics/__init__.py index 13a83393a91..e69de29bb2d 100644 --- a/core/amber/src/main/python/proto/edu/uci/ics/__init__.py +++ b/core/amber/src/main/python/proto/edu/uci/ics/__init__.py @@ -1,16 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. diff --git a/core/amber/src/main/python/proto/edu/uci/ics/amber/__init__.py b/core/amber/src/main/python/proto/edu/uci/ics/amber/__init__.py index 13a83393a91..e69de29bb2d 100644 --- a/core/amber/src/main/python/proto/edu/uci/ics/amber/__init__.py +++ b/core/amber/src/main/python/proto/edu/uci/ics/amber/__init__.py @@ -1,16 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. diff --git a/core/amber/src/main/python/proto/edu/uci/ics/amber/core/__init__.py b/core/amber/src/main/python/proto/edu/uci/ics/amber/core/__init__.py index f155542dede..cc12d01852e 100644 --- a/core/amber/src/main/python/proto/edu/uci/ics/amber/core/__init__.py +++ b/core/amber/src/main/python/proto/edu/uci/ics/amber/core/__init__.py @@ -1,20 +1,3 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. - # Generated by the protocol buffer compiler. DO NOT EDIT! # sources: edu/uci/ics/amber/core/executor.proto, edu/uci/ics/amber/core/virtualidentity.proto, edu/uci/ics/amber/core/workflow.proto, edu/uci/ics/amber/core/workflowruntimestate.proto # plugin: python-betterproto @@ -81,7 +64,7 @@ class PhysicalOpIdentity(betterproto.Message): @dataclass(eq=False, repr=False) -class ChannelMarkerIdentity(betterproto.Message): +class EmbeddedControlMessageIdentity(betterproto.Message): id: str = betterproto.string_field(1) diff --git a/core/amber/src/main/python/proto/edu/uci/ics/amber/engine/__init__.py b/core/amber/src/main/python/proto/edu/uci/ics/amber/engine/__init__.py index 13a83393a91..e69de29bb2d 100644 --- a/core/amber/src/main/python/proto/edu/uci/ics/amber/engine/__init__.py +++ b/core/amber/src/main/python/proto/edu/uci/ics/amber/engine/__init__.py @@ -1,16 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. diff --git a/core/amber/src/main/python/proto/edu/uci/ics/amber/engine/architecture/__init__.py b/core/amber/src/main/python/proto/edu/uci/ics/amber/engine/architecture/__init__.py index 13a83393a91..e69de29bb2d 100644 --- a/core/amber/src/main/python/proto/edu/uci/ics/amber/engine/architecture/__init__.py +++ b/core/amber/src/main/python/proto/edu/uci/ics/amber/engine/architecture/__init__.py @@ -1,16 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. diff --git a/core/amber/src/main/python/proto/edu/uci/ics/amber/engine/architecture/rpc/__init__.py b/core/amber/src/main/python/proto/edu/uci/ics/amber/engine/architecture/rpc/__init__.py index b75943f2017..23ae9df4215 100644 --- a/core/amber/src/main/python/proto/edu/uci/ics/amber/engine/architecture/rpc/__init__.py +++ b/core/amber/src/main/python/proto/edu/uci/ics/amber/engine/architecture/rpc/__init__.py @@ -1,20 +1,3 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. - # Generated by the protocol buffer compiler. DO NOT EDIT! # sources: edu/uci/ics/amber/engine/architecture/rpc/controlcommands.proto, edu/uci/ics/amber/engine/architecture/rpc/controllerservice.proto, edu/uci/ics/amber/engine/architecture/rpc/controlreturns.proto, edu/uci/ics/amber/engine/architecture/rpc/testerservice.proto, edu/uci/ics/amber/engine/architecture/rpc/workerservice.proto # plugin: python-betterproto @@ -47,9 +30,7 @@ from grpclib.metadata import Deadline -class ChannelMarkerType(betterproto.Enum): - """Enum for ChannelMarkerType""" - +class EmbeddedControlMessageType(betterproto.Enum): ALL_ALIGNMENT = 0 NO_ALIGNMENT = 1 PORT_ALIGNMENT = 2 @@ -82,9 +63,9 @@ class WorkflowAggregatedState(betterproto.Enum): @dataclass(eq=False, repr=False) class ControlRequest(betterproto.Message): - propagate_channel_marker_request: "PropagateChannelMarkerRequest" = ( - betterproto.message_field(1, group="sealed_value") - ) + propagate_embedded_control_message_request: ( + "PropagateEmbeddedControlMessageRequest" + ) = betterproto.message_field(1, group="sealed_value") """request for controller""" take_global_checkpoint_request: "TakeGlobalCheckpointRequest" = ( @@ -177,11 +158,9 @@ class ControlInvocation(betterproto.Message): @dataclass(eq=False, repr=False) -class ChannelMarkerPayload(betterproto.Message): - """Message for ChannelMarkerPayload""" - - id: "___core__.ChannelMarkerIdentity" = betterproto.message_field(1) - marker_type: "ChannelMarkerType" = betterproto.enum_field(2) +class EmbeddedControlMessage(betterproto.Message): + id: "___core__.EmbeddedControlMessageIdentity" = betterproto.message_field(1) + ecm_type: "EmbeddedControlMessageType" = betterproto.enum_field(2) scope: List["___core__.ChannelIdentity"] = betterproto.message_field(3) command_mapping: Dict[str, "ControlInvocation"] = betterproto.map_field( 4, betterproto.TYPE_STRING, betterproto.TYPE_MESSAGE @@ -189,12 +168,12 @@ class ChannelMarkerPayload(betterproto.Message): @dataclass(eq=False, repr=False) -class PropagateChannelMarkerRequest(betterproto.Message): +class PropagateEmbeddedControlMessageRequest(betterproto.Message): source_op_to_start_prop: List["___core__.PhysicalOpIdentity"] = ( betterproto.message_field(1) ) - id: "___core__.ChannelMarkerIdentity" = betterproto.message_field(2) - marker_type: "ChannelMarkerType" = betterproto.enum_field(3) + id: "___core__.EmbeddedControlMessageIdentity" = betterproto.message_field(2) + ecm_type: "EmbeddedControlMessageType" = betterproto.enum_field(3) scope: List["___core__.PhysicalOpIdentity"] = betterproto.message_field(4) target_ops: List["___core__.PhysicalOpIdentity"] = betterproto.message_field(5) marker_command: "ControlRequest" = betterproto.message_field(6) @@ -204,7 +183,9 @@ class PropagateChannelMarkerRequest(betterproto.Message): @dataclass(eq=False, repr=False) class TakeGlobalCheckpointRequest(betterproto.Message): estimation_only: bool = betterproto.bool_field(1) - checkpoint_id: "___core__.ChannelMarkerIdentity" = betterproto.message_field(2) + checkpoint_id: "___core__.EmbeddedControlMessageIdentity" = ( + betterproto.message_field(2) + ) destination: str = betterproto.string_field(3) @@ -368,7 +349,9 @@ class AssignPortRequest(betterproto.Message): @dataclass(eq=False, repr=False) class FinalizeCheckpointRequest(betterproto.Message): - checkpoint_id: "___core__.ChannelMarkerIdentity" = betterproto.message_field(1) + checkpoint_id: "___core__.EmbeddedControlMessageIdentity" = ( + betterproto.message_field(1) + ) write_to: str = betterproto.string_field(2) @@ -390,7 +373,9 @@ class UpdateExecutorRequest(betterproto.Message): @dataclass(eq=False, repr=False) class PrepareCheckpointRequest(betterproto.Message): - checkpoint_id: "___core__.ChannelMarkerIdentity" = betterproto.message_field(1) + checkpoint_id: "___core__.EmbeddedControlMessageIdentity" = ( + betterproto.message_field(1) + ) estimation_only: bool = betterproto.bool_field(2) @@ -410,9 +395,9 @@ class ControlReturn(betterproto.Message): ) """controller responses""" - propagate_channel_marker_response: "PropagateChannelMarkerResponse" = ( - betterproto.message_field(2, group="sealed_value") - ) + propagate_embedded_control_message_response: ( + "PropagateEmbeddedControlMessageResponse" + ) = betterproto.message_field(2, group="sealed_value") take_global_checkpoint_response: "TakeGlobalCheckpointResponse" = ( betterproto.message_field(3, group="sealed_value") ) @@ -485,7 +470,7 @@ class FinalizeCheckpointResponse(betterproto.Message): @dataclass(eq=False, repr=False) -class PropagateChannelMarkerResponse(betterproto.Message): +class PropagateEmbeddedControlMessageResponse(betterproto.Message): returns: Dict[str, "ControlReturn"] = betterproto.map_field( 1, betterproto.TYPE_STRING, betterproto.TYPE_MESSAGE ) @@ -549,18 +534,18 @@ async def retrieve_workflow_state( metadata=metadata, ) - async def propagate_channel_marker( + async def propagate_embedded_control_message( self, - propagate_channel_marker_request: "PropagateChannelMarkerRequest", + propagate_embedded_control_message_request: "PropagateEmbeddedControlMessageRequest", *, timeout: Optional[float] = None, deadline: Optional["Deadline"] = None, metadata: Optional["MetadataLike"] = None - ) -> "PropagateChannelMarkerResponse": + ) -> "PropagateEmbeddedControlMessageResponse": return await self._unary_unary( - "/edu.uci.ics.amber.engine.architecture.rpc.ControllerService/PropagateChannelMarker", - propagate_channel_marker_request, - PropagateChannelMarkerResponse, + "/edu.uci.ics.amber.engine.architecture.rpc.ControllerService/PropagateEmbeddedControlMessage", + propagate_embedded_control_message_request, + PropagateEmbeddedControlMessageResponse, timeout=timeout, deadline=deadline, metadata=metadata, @@ -1292,9 +1277,10 @@ async def retrieve_workflow_state( ) -> "RetrieveWorkflowStateResponse": raise grpclib.GRPCError(grpclib.const.Status.UNIMPLEMENTED) - async def propagate_channel_marker( - self, propagate_channel_marker_request: "PropagateChannelMarkerRequest" - ) -> "PropagateChannelMarkerResponse": + async def propagate_embedded_control_message( + self, + propagate_embedded_control_message_request: "PropagateEmbeddedControlMessageRequest", + ) -> "PropagateEmbeddedControlMessageResponse": raise grpclib.GRPCError(grpclib.const.Status.UNIMPLEMENTED) async def take_global_checkpoint( @@ -1366,12 +1352,12 @@ async def __rpc_retrieve_workflow_state( response = await self.retrieve_workflow_state(request) await stream.send_message(response) - async def __rpc_propagate_channel_marker( + async def __rpc_propagate_embedded_control_message( self, - stream: "grpclib.server.Stream[PropagateChannelMarkerRequest, PropagateChannelMarkerResponse]", + stream: "grpclib.server.Stream[PropagateEmbeddedControlMessageRequest, PropagateEmbeddedControlMessageResponse]", ) -> None: request = await stream.recv_message() - response = await self.propagate_channel_marker(request) + response = await self.propagate_embedded_control_message(request) await stream.send_message(response) async def __rpc_take_global_checkpoint( @@ -1476,11 +1462,11 @@ def __mapping__(self) -> Dict[str, grpclib.const.Handler]: EmptyRequest, RetrieveWorkflowStateResponse, ), - "/edu.uci.ics.amber.engine.architecture.rpc.ControllerService/PropagateChannelMarker": grpclib.const.Handler( - self.__rpc_propagate_channel_marker, + "/edu.uci.ics.amber.engine.architecture.rpc.ControllerService/PropagateEmbeddedControlMessage": grpclib.const.Handler( + self.__rpc_propagate_embedded_control_message, grpclib.const.Cardinality.UNARY_UNARY, - PropagateChannelMarkerRequest, - PropagateChannelMarkerResponse, + PropagateEmbeddedControlMessageRequest, + PropagateEmbeddedControlMessageResponse, ), "/edu.uci.ics.amber.engine.architecture.rpc.ControllerService/TakeGlobalCheckpoint": grpclib.const.Handler( self.__rpc_take_global_checkpoint, diff --git a/core/amber/src/main/python/proto/edu/uci/ics/amber/engine/architecture/sendsemantics/__init__.py b/core/amber/src/main/python/proto/edu/uci/ics/amber/engine/architecture/sendsemantics/__init__.py index 0b10145b8a2..b9769dc2bb9 100644 --- a/core/amber/src/main/python/proto/edu/uci/ics/amber/engine/architecture/sendsemantics/__init__.py +++ b/core/amber/src/main/python/proto/edu/uci/ics/amber/engine/architecture/sendsemantics/__init__.py @@ -1,20 +1,3 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. - # Generated by the protocol buffer compiler. DO NOT EDIT! # sources: edu/uci/ics/amber/engine/architecture/sendsemantics/partitionings.proto # plugin: python-betterproto diff --git a/core/amber/src/main/python/proto/edu/uci/ics/amber/engine/architecture/worker/__init__.py b/core/amber/src/main/python/proto/edu/uci/ics/amber/engine/architecture/worker/__init__.py index f5fd615493d..8b87e847f58 100644 --- a/core/amber/src/main/python/proto/edu/uci/ics/amber/engine/architecture/worker/__init__.py +++ b/core/amber/src/main/python/proto/edu/uci/ics/amber/engine/architecture/worker/__init__.py @@ -1,20 +1,3 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. - # Generated by the protocol buffer compiler. DO NOT EDIT! # sources: edu/uci/ics/amber/engine/architecture/worker/statistics.proto # plugin: python-betterproto diff --git a/core/amber/src/main/python/proto/edu/uci/ics/amber/engine/common/__init__.py b/core/amber/src/main/python/proto/edu/uci/ics/amber/engine/common/__init__.py index 061f0370aa4..48771d4c814 100644 --- a/core/amber/src/main/python/proto/edu/uci/ics/amber/engine/common/__init__.py +++ b/core/amber/src/main/python/proto/edu/uci/ics/amber/engine/common/__init__.py @@ -1,20 +1,3 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. - # Generated by the protocol buffer compiler. DO NOT EDIT! # sources: edu/uci/ics/amber/engine/common/actormessage.proto, edu/uci/ics/amber/engine/common/ambermessage.proto, edu/uci/ics/amber/engine/common/executionruntimestate.proto # plugin: python-betterproto diff --git a/core/amber/src/main/python/proto/scalapb/__init__.py b/core/amber/src/main/python/proto/scalapb/__init__.py index 153dd1b07aa..49c713815a5 100644 --- a/core/amber/src/main/python/proto/scalapb/__init__.py +++ b/core/amber/src/main/python/proto/scalapb/__init__.py @@ -1,20 +1,3 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. - # Generated by the protocol buffer compiler. DO NOT EDIT! # sources: scalapb/scalapb.proto # plugin: python-betterproto diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/controller/Controller.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/controller/Controller.scala index c31d3a1c060..83616a688c2 100644 --- a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/controller/Controller.scala +++ b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/controller/Controller.scala @@ -27,7 +27,7 @@ import edu.uci.ics.amber.engine.architecture.common.{ExecutorDeployment, Workflo import edu.uci.ics.amber.engine.architecture.common.WorkflowActor.NetworkAck import edu.uci.ics.amber.engine.architecture.controller.execution.OperatorExecution import edu.uci.ics.amber.engine.architecture.rpc.controlcommands.{ - ChannelMarkerPayload, + EmbeddedControlMessage, ControlInvocation } import edu.uci.ics.amber.engine.architecture.worker.WorkflowWorker.{ @@ -139,7 +139,7 @@ class Controller( logManager.withFaultTolerant(msg.channelId, msgToLog) { msg.payload match { case payload: ControlPayload => cp.processControlPayload(msg.channelId, payload) - case marker: ChannelMarkerPayload => // skip marker + case _: EmbeddedControlMessage => // skip ECM case p => throw new RuntimeException(s"controller cannot handle $p") } } diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/controller/ControllerAsyncRPCHandlerInitializer.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/controller/ControllerAsyncRPCHandlerInitializer.scala index de075710c96..c1975ac350e 100644 --- a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/controller/ControllerAsyncRPCHandlerInitializer.scala +++ b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/controller/ControllerAsyncRPCHandlerInitializer.scala @@ -45,7 +45,7 @@ class ControllerAsyncRPCHandlerInitializer( with EvaluatePythonExpressionHandler with DebugCommandHandler with TakeGlobalCheckpointHandler - with ChannelMarkerHandler + with EmbeddedControlMessageHandler with RetrieveWorkflowStateHandler { val actorId: ActorVirtualIdentity = cp.actorId } diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/controller/promisehandlers/ChannelMarkerHandler.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/controller/promisehandlers/EmbeddedControlMessageHandler.scala similarity index 90% rename from core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/controller/promisehandlers/ChannelMarkerHandler.scala rename to core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/controller/promisehandlers/EmbeddedControlMessageHandler.scala index eb3a7efe535..3ecffacc6f6 100644 --- a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/controller/promisehandlers/ChannelMarkerHandler.scala +++ b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/controller/promisehandlers/EmbeddedControlMessageHandler.scala @@ -24,23 +24,23 @@ import edu.uci.ics.amber.engine.architecture.controller.ControllerAsyncRPCHandle import edu.uci.ics.amber.engine.architecture.rpc.controlcommands.{ AsyncRPCContext, ControlInvocation, - PropagateChannelMarkerRequest + PropagateEmbeddedControlMessageRequest } import edu.uci.ics.amber.engine.architecture.rpc.controlreturns.{ ControlReturn, - PropagateChannelMarkerResponse + PropagateEmbeddedControlMessageResponse } import edu.uci.ics.amber.engine.common.virtualidentity.util.CONTROLLER import edu.uci.ics.amber.util.VirtualIdentityUtils import edu.uci.ics.amber.core.virtualidentity.{ActorVirtualIdentity, ChannelIdentity} -trait ChannelMarkerHandler { +trait EmbeddedControlMessageHandler { this: ControllerAsyncRPCHandlerInitializer => - override def propagateChannelMarker( - msg: PropagateChannelMarkerRequest, + override def propagateEmbeddedControlMessage( + msg: PropagateEmbeddedControlMessageRequest, ctx: AsyncRPCContext - ): Future[PropagateChannelMarkerResponse] = { + ): Future[PropagateEmbeddedControlMessageResponse] = { // step1: create separate control commands for each target actor. val inputSet = msg.targetOps.flatMap { target => cp.workflowExecution.getRunningRegionExecutions @@ -84,9 +84,9 @@ trait ChannelMarkerHandler { // step 4: start prop, send marker through control channel with the compound command from sources. msg.sourceOpToStartProp.foreach { source => cp.workflowExecution.getLatestOperatorExecution(source).getWorkerIds.foreach { worker => - sendChannelMarker( + sendECM( msg.id, - msg.markerType, + msg.ecmType, finalScope.toSet, cmdMapping, ChannelIdentity(actorId, worker, isControl = true) @@ -97,7 +97,7 @@ trait ChannelMarkerHandler { // step 5: wait for the marker propagation. Future.collect(futures.toList).map { ret => cp.logManager.markAsReplayDestination(msg.id) - PropagateChannelMarkerResponse(ret.map(x => (x._1.name, x._2)).toMap) + PropagateEmbeddedControlMessageResponse(ret.map(x => (x._1.name, x._2)).toMap) } } } diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/controller/promisehandlers/RetrieveWorkflowStateHandler.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/controller/promisehandlers/RetrieveWorkflowStateHandler.scala index 436aa531553..d70ef541122 100644 --- a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/controller/promisehandlers/RetrieveWorkflowStateHandler.scala +++ b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/controller/promisehandlers/RetrieveWorkflowStateHandler.scala @@ -21,11 +21,11 @@ package edu.uci.ics.amber.engine.architecture.controller.promisehandlers import com.twitter.util.Future import edu.uci.ics.amber.engine.architecture.controller.ControllerAsyncRPCHandlerInitializer -import edu.uci.ics.amber.engine.architecture.rpc.controlcommands.ChannelMarkerType.NO_ALIGNMENT +import edu.uci.ics.amber.engine.architecture.rpc.controlcommands.EmbeddedControlMessageType.NO_ALIGNMENT import edu.uci.ics.amber.engine.architecture.rpc.controlcommands.{ AsyncRPCContext, EmptyRequest, - PropagateChannelMarkerRequest + PropagateEmbeddedControlMessageRequest } import edu.uci.ics.amber.engine.architecture.rpc.controlreturns.{ RetrieveWorkflowStateResponse, @@ -33,7 +33,7 @@ import edu.uci.ics.amber.engine.architecture.rpc.controlreturns.{ } import edu.uci.ics.amber.engine.architecture.rpc.workerservice.WorkerServiceGrpc.METHOD_RETRIEVE_STATE import edu.uci.ics.amber.engine.common.virtualidentity.util.SELF -import edu.uci.ics.amber.core.virtualidentity.ChannelMarkerIdentity +import edu.uci.ics.amber.core.virtualidentity.EmbeddedControlMessageIdentity import java.time.Instant @@ -45,11 +45,11 @@ trait RetrieveWorkflowStateHandler { ctx: AsyncRPCContext ): Future[RetrieveWorkflowStateResponse] = { val targetOps = cp.workflowScheduler.physicalPlan.operators.map(_.id).toSeq - val markerMessage = PropagateChannelMarkerRequest( + val markerMessage = PropagateEmbeddedControlMessageRequest( cp.workflowExecution.getRunningRegionExecutions .flatMap(_.getAllOperatorExecutions.map(_._1)) .toSeq, - ChannelMarkerIdentity("RetrieveWorkflowState_" + Instant.now().toString), + EmbeddedControlMessageIdentity("RetrieveWorkflowState_" + Instant.now().toString), NO_ALIGNMENT, targetOps, targetOps, @@ -57,7 +57,7 @@ trait RetrieveWorkflowStateHandler { METHOD_RETRIEVE_STATE.getBareMethodName ) controllerInterface - .propagateChannelMarker( + .propagateEmbeddedControlMessage( markerMessage, mkContext(SELF) ) diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/controller/promisehandlers/TakeGlobalCheckpointHandler.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/controller/promisehandlers/TakeGlobalCheckpointHandler.scala index 930f74361bc..cac92760559 100644 --- a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/controller/promisehandlers/TakeGlobalCheckpointHandler.scala +++ b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/controller/promisehandlers/TakeGlobalCheckpointHandler.scala @@ -21,7 +21,7 @@ package edu.uci.ics.amber.engine.architecture.controller.promisehandlers import com.twitter.util.Future import edu.uci.ics.amber.engine.architecture.controller.ControllerAsyncRPCHandlerInitializer -import edu.uci.ics.amber.engine.architecture.rpc.controlcommands.ChannelMarkerType.NO_ALIGNMENT +import edu.uci.ics.amber.engine.architecture.rpc.controlcommands.EmbeddedControlMessageType.NO_ALIGNMENT import edu.uci.ics.amber.engine.architecture.rpc.controlcommands._ import edu.uci.ics.amber.engine.architecture.rpc.controlreturns.TakeGlobalCheckpointResponse import edu.uci.ics.amber.engine.architecture.rpc.workerservice.WorkerServiceGrpc.METHOD_PREPARE_CHECKPOINT @@ -51,8 +51,8 @@ trait TakeGlobalCheckpointHandler { var totalSize = 0L val physicalOpIdsToTakeCheckpoint = cp.workflowScheduler.physicalPlan.operators.map(_.id) controllerInterface - .propagateChannelMarker( - PropagateChannelMarkerRequest( + .propagateEmbeddedControlMessage( + PropagateEmbeddedControlMessageRequest( cp.workflowExecution.getAllRegionExecutions .flatMap(_.getAllOperatorExecutions.map(_._1)) .toSeq, diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/logreplay/EmptyReplayLogger.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/logreplay/EmptyReplayLogger.scala index 8419a6f0d98..b7d50aaf11a 100644 --- a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/logreplay/EmptyReplayLogger.scala +++ b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/logreplay/EmptyReplayLogger.scala @@ -20,7 +20,7 @@ package edu.uci.ics.amber.engine.architecture.logreplay import edu.uci.ics.amber.engine.common.ambermessage.WorkflowFIFOMessage -import edu.uci.ics.amber.core.virtualidentity.{ChannelIdentity, ChannelMarkerIdentity} +import edu.uci.ics.amber.core.virtualidentity.{ChannelIdentity, EmbeddedControlMessageIdentity} class EmptyReplayLogger extends ReplayLogger { @@ -28,7 +28,7 @@ class EmptyReplayLogger extends ReplayLogger { Array.empty } - def markAsReplayDestination(id: ChannelMarkerIdentity): Unit = {} + def markAsReplayDestination(id: EmbeddedControlMessageIdentity): Unit = {} override def logCurrentStepWithMessage( step: Long, diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/logreplay/ReplayLogGenerator.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/logreplay/ReplayLogGenerator.scala index d6850aaff4b..5933041719c 100644 --- a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/logreplay/ReplayLogGenerator.scala +++ b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/logreplay/ReplayLogGenerator.scala @@ -21,7 +21,7 @@ package edu.uci.ics.amber.engine.architecture.logreplay import edu.uci.ics.amber.engine.common.ambermessage.WorkflowFIFOMessage import edu.uci.ics.amber.engine.common.storage.SequentialRecordStorage -import edu.uci.ics.amber.core.virtualidentity.ChannelMarkerIdentity +import edu.uci.ics.amber.core.virtualidentity.EmbeddedControlMessageIdentity import scala.collection.mutable @@ -29,7 +29,7 @@ object ReplayLogGenerator { def generate( logStorage: SequentialRecordStorage[ReplayLogRecord], logFileName: String, - replayTo: ChannelMarkerIdentity + replayTo: EmbeddedControlMessageIdentity ): (mutable.Queue[ProcessingStep], mutable.Queue[WorkflowFIFOMessage]) = { val logs = logStorage.getReader(logFileName).mkRecordIterator() val steps = mutable.Queue[ProcessingStep]() diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/logreplay/ReplayLogManager.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/logreplay/ReplayLogManager.scala index 20d1bebf1d4..c7b1e8347c6 100644 --- a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/logreplay/ReplayLogManager.scala +++ b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/logreplay/ReplayLogManager.scala @@ -24,7 +24,7 @@ import edu.uci.ics.amber.engine.architecture.worker.WorkflowWorker.MainThreadDel import edu.uci.ics.amber.engine.common.ambermessage.WorkflowFIFOMessage import edu.uci.ics.amber.engine.common.storage.SequentialRecordStorage.SequentialRecordWriter import edu.uci.ics.amber.engine.common.storage.{EmptyRecordStorage, SequentialRecordStorage} -import edu.uci.ics.amber.core.virtualidentity.{ChannelIdentity, ChannelMarkerIdentity} +import edu.uci.ics.amber.core.virtualidentity.{ChannelIdentity, EmbeddedControlMessageIdentity} //In-mem formats: sealed trait ReplayLogRecord extends Serializable @@ -33,7 +33,7 @@ case class MessageContent(message: WorkflowFIFOMessage) extends ReplayLogRecord case class ProcessingStep(channelId: ChannelIdentity, step: Long) extends ReplayLogRecord -case class ReplayDestination(id: ChannelMarkerIdentity) extends ReplayLogRecord +case class ReplayDestination(id: EmbeddedControlMessageIdentity) extends ReplayLogRecord case object TerminateSignal extends ReplayLogRecord @@ -66,7 +66,7 @@ trait ReplayLogManager { def getStep: Long = cursor.getStep - def markAsReplayDestination(id: ChannelMarkerIdentity): Unit + def markAsReplayDestination(id: EmbeddedControlMessageIdentity): Unit def withFaultTolerant( channelId: ChannelIdentity, @@ -97,7 +97,7 @@ class EmptyReplayLogManagerImpl( override def terminate(): Unit = {} - override def markAsReplayDestination(id: ChannelMarkerIdentity): Unit = {} + override def markAsReplayDestination(id: EmbeddedControlMessageIdentity): Unit = {} } class ReplayLogManagerImpl(handler: Either[MainThreadDelegateMessage, WorkflowFIFOMessage] => Unit) @@ -115,7 +115,7 @@ class ReplayLogManagerImpl(handler: Either[MainThreadDelegateMessage, WorkflowFI super.withFaultTolerant(channelId, message)(code) } - override def markAsReplayDestination(id: ChannelMarkerIdentity): Unit = { + override def markAsReplayDestination(id: EmbeddedControlMessageIdentity): Unit = { replayLogger.markAsReplayDestination(id) } diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/logreplay/ReplayLogger.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/logreplay/ReplayLogger.scala index a83a5e8ede1..f1d2680f432 100644 --- a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/logreplay/ReplayLogger.scala +++ b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/logreplay/ReplayLogger.scala @@ -20,7 +20,7 @@ package edu.uci.ics.amber.engine.architecture.logreplay import edu.uci.ics.amber.engine.common.ambermessage.WorkflowFIFOMessage -import edu.uci.ics.amber.core.virtualidentity.{ChannelIdentity, ChannelMarkerIdentity} +import edu.uci.ics.amber.core.virtualidentity.{ChannelIdentity, EmbeddedControlMessageIdentity} abstract class ReplayLogger { @@ -30,7 +30,7 @@ abstract class ReplayLogger { msg: Option[WorkflowFIFOMessage] ): Unit - def markAsReplayDestination(id: ChannelMarkerIdentity): Unit + def markAsReplayDestination(id: EmbeddedControlMessageIdentity): Unit def drainCurrentLogRecords(step: Long): Array[ReplayLogRecord] diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/logreplay/ReplayLoggerImpl.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/logreplay/ReplayLoggerImpl.scala index fe1f9127958..12b90b0888d 100644 --- a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/logreplay/ReplayLoggerImpl.scala +++ b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/logreplay/ReplayLoggerImpl.scala @@ -21,7 +21,7 @@ package edu.uci.ics.amber.engine.architecture.logreplay import edu.uci.ics.amber.engine.architecture.common.ProcessingStepCursor.INIT_STEP import edu.uci.ics.amber.engine.common.ambermessage.WorkflowFIFOMessage -import edu.uci.ics.amber.core.virtualidentity.{ChannelIdentity, ChannelMarkerIdentity} +import edu.uci.ics.amber.core.virtualidentity.{ChannelIdentity, EmbeddedControlMessageIdentity} import scala.collection.mutable @@ -77,7 +77,7 @@ class ReplayLoggerImpl extends ReplayLogger { result } - def markAsReplayDestination(id: ChannelMarkerIdentity): Unit = { + def markAsReplayDestination(id: EmbeddedControlMessageIdentity): Unit = { tempLogs.append(ReplayDestination(id)) } } diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/pythonworker/PythonProxyClient.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/pythonworker/PythonProxyClient.scala index d7e1bdfe0a5..d3fb4fb6977 100644 --- a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/pythonworker/PythonProxyClient.scala +++ b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/pythonworker/PythonProxyClient.scala @@ -26,12 +26,12 @@ import edu.uci.ics.amber.core.tuple.{Schema, Tuple} import edu.uci.ics.amber.core.virtualidentity.{ActorVirtualIdentity, ChannelIdentity} import edu.uci.ics.amber.engine.architecture.pythonworker.WorkerBatchInternalQueue.{ ActorCommandElement, - ChannelMarkerElement, + EmbeddedControlMessageElement, ControlElement, DataElement } import edu.uci.ics.amber.engine.architecture.rpc.controlcommands.{ - ChannelMarkerPayload, + EmbeddedControlMessage, ControlInvocation } import edu.uci.ics.amber.engine.architecture.rpc.controlreturns.ReturnInvocation @@ -114,8 +114,8 @@ class PythonProxyClient(portNumberPromise: Promise[Int], val actorId: ActorVirtu sendData(dataPayload, channel) case ControlElement(cmd, channel) => sendControl(channel, cmd) - case ChannelMarkerElement(cmd, channel) => - sendChannelMarker(cmd, channel) + case EmbeddedControlMessageElement(cmd, channel) => + sendECM(cmd, channel) case ActorCommandElement(cmd) => sendActorCommand(cmd) } @@ -131,8 +131,8 @@ class PythonProxyClient(portNumberPromise: Promise[Int], val actorId: ActorVirtu } } - private def sendChannelMarker( - markerPayload: ChannelMarkerPayload, + private def sendECM( + ecm: EmbeddedControlMessage, from: ChannelIdentity ): Unit = { val descriptor = FlightDescriptor.command(PythonDataHeader(from, "ChannelMarker").toByteArray) @@ -146,7 +146,7 @@ class PythonProxyClient(portNumberPromise: Promise[Int], val actorId: ActorVirtu schemaRoot.allocateNew() val vector = schemaRoot.getVector("payload").asInstanceOf[VarBinaryVector] - vector.setSafe(0, markerPayload.toByteArray) + vector.setSafe(0, ecm.toByteArray) vector.setValueCount(1) schemaRoot.setRowCount(1) diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/pythonworker/PythonProxyServer.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/pythonworker/PythonProxyServer.scala index ee2eff5e173..6b400c6166c 100644 --- a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/pythonworker/PythonProxyServer.scala +++ b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/pythonworker/PythonProxyServer.scala @@ -25,7 +25,7 @@ import edu.uci.ics.amber.core.state.State import edu.uci.ics.amber.core.tuple.Tuple import edu.uci.ics.amber.core.virtualidentity.{ActorVirtualIdentity, ChannelIdentity} import edu.uci.ics.amber.engine.architecture.messaginglayer.NetworkOutputGateway -import edu.uci.ics.amber.engine.architecture.rpc.controlcommands.ChannelMarkerPayload +import edu.uci.ics.amber.engine.architecture.rpc.controlcommands.EmbeddedControlMessage import edu.uci.ics.amber.engine.common.AmberLogging import edu.uci.ics.amber.engine.common.ambermessage.ControlPayloadV2.Value.{ ControlInvocation => ControlInvocationV2, @@ -133,7 +133,7 @@ private class AmberProducer( assert(root.getRowCount == 1) outputPort.sendTo( to, - ChannelMarkerPayload.parseFrom( + EmbeddedControlMessage.parseFrom( root.getVector("payload").asInstanceOf[VarBinaryVector].get(0) ) ) diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/pythonworker/PythonWorkflowWorker.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/pythonworker/PythonWorkflowWorker.scala index 56d4eaaeed1..38638bca93d 100644 --- a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/pythonworker/PythonWorkflowWorker.scala +++ b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/pythonworker/PythonWorkflowWorker.scala @@ -30,10 +30,10 @@ import edu.uci.ics.amber.engine.architecture.messaginglayer.{ NetworkOutputGateway } import edu.uci.ics.amber.engine.architecture.pythonworker.WorkerBatchInternalQueue.{ - ChannelMarkerElement, + EmbeddedControlMessageElement, DataElement } -import edu.uci.ics.amber.engine.architecture.rpc.controlcommands.ChannelMarkerPayload +import edu.uci.ics.amber.engine.architecture.rpc.controlcommands.EmbeddedControlMessage import edu.uci.ics.amber.engine.architecture.scheduling.config.WorkerConfig import edu.uci.ics.amber.engine.common.actormessage.{Backpressure, CreditUpdate} import edu.uci.ics.amber.engine.common.ambermessage.WorkflowMessage.getInMemSize @@ -90,8 +90,8 @@ class PythonWorkflowWorker( pythonProxyClient.enqueueCommand(payload, workflowMsg.channelId) case payload: DataPayload => pythonProxyClient.enqueueData(DataElement(payload, workflowMsg.channelId)) - case marker: ChannelMarkerPayload => - pythonProxyClient.enqueueData(ChannelMarkerElement(marker, workflowMsg.channelId)) + case ecm: EmbeddedControlMessage => + pythonProxyClient.enqueueData(EmbeddedControlMessageElement(ecm, workflowMsg.channelId)) case p => logger.error(s"unhandled control payload: $p") } } diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/pythonworker/WorkerBatchInternalQueue.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/pythonworker/WorkerBatchInternalQueue.scala index 4c0c5b0244e..66964382c28 100644 --- a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/pythonworker/WorkerBatchInternalQueue.scala +++ b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/pythonworker/WorkerBatchInternalQueue.scala @@ -23,7 +23,7 @@ import edu.uci.ics.amber.engine.architecture.pythonworker.WorkerBatchInternalQue import edu.uci.ics.amber.engine.common.actormessage.ActorCommand import edu.uci.ics.amber.engine.common.ambermessage.{ControlPayload, DataFrame, DataPayload} import edu.uci.ics.amber.core.virtualidentity.ChannelIdentity -import edu.uci.ics.amber.engine.architecture.rpc.controlcommands.ChannelMarkerPayload +import edu.uci.ics.amber.engine.architecture.rpc.controlcommands.EmbeddedControlMessage import lbmq.LinkedBlockingMultiQueue import scala.collection.mutable @@ -39,7 +39,7 @@ object WorkerBatchInternalQueue { extends InternalQueueElement case class ControlElement(cmd: ControlPayload, from: ChannelIdentity) extends InternalQueueElement - case class ChannelMarkerElement(cmd: ChannelMarkerPayload, from: ChannelIdentity) + case class EmbeddedControlMessageElement(cmd: EmbeddedControlMessage, from: ChannelIdentity) extends InternalQueueElement case class ActorCommandElement(cmd: ActorCommand) extends InternalQueueElement } diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/ChannelMarkerManager.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/ChannelMarkerManager.scala deleted file mode 100644 index f4eaebc8ff6..00000000000 --- a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/ChannelMarkerManager.scala +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package edu.uci.ics.amber.engine.architecture.worker - -import edu.uci.ics.amber.engine.architecture.messaginglayer.{InputGateway, InputManager} -import edu.uci.ics.amber.engine.architecture.rpc.controlcommands.ChannelMarkerPayload -import edu.uci.ics.amber.engine.architecture.rpc.controlcommands.ChannelMarkerType.{ - NO_ALIGNMENT, - PORT_ALIGNMENT, - ALL_ALIGNMENT -} -import edu.uci.ics.amber.engine.common.{AmberLogging, CheckpointState} -import edu.uci.ics.amber.core.virtualidentity.{ - ActorVirtualIdentity, - ChannelIdentity, - ChannelMarkerIdentity -} - -import scala.collection.mutable - -class ChannelMarkerManager( - val actorId: ActorVirtualIdentity, - inputGateway: InputGateway, - inputManager: InputManager -) extends AmberLogging { - - private val markerReceived = new mutable.HashMap[ChannelMarkerIdentity, Set[ChannelIdentity]]() - - val checkpoints = new mutable.HashMap[ChannelMarkerIdentity, CheckpointState]() - - /** - * Determines if an epoch marker is fully received from all relevant senders within its scope. - * This method checks if the epoch marker, based on its type, has been received from all necessary channels. - * For markers requiring alignment, it verifies receipt from all senders in the scope. For non-aligned markers, - * it checks if it's the first received marker. Post verification, it cleans up the markers. - * - * @return Boolean indicating if the epoch marker is completely received from all senders - * within the scope. Returns true if the marker is aligned, otherwise false. - */ - def isMarkerAligned( - from: ChannelIdentity, - marker: ChannelMarkerPayload - ): Boolean = { - val markerId = marker.id - val portId = inputGateway.getChannel(from).getPortId - if (!markerReceived.contains(markerId)) { - markerReceived(markerId) = Set() - } - markerReceived.update(markerId, markerReceived(markerId) + from) - val markerReceivedFromAllChannels = - getChannelsWithinScope(marker).subsetOf(markerReceived(markerId)) - // check if the epoch marker is completed - val epochMarkerCompleted = marker.markerType match { - case ALL_ALIGNMENT => - markerReceivedFromAllChannels - case PORT_ALIGNMENT => - inputManager.getPort(portId).channels.subsetOf(markerReceived(markerId)) - case NO_ALIGNMENT => - markerReceived(markerId).size == 1 // only the first marker triggers - case _ => - throw new IllegalArgumentException( - s"Unsupported marker type: ${marker.markerType}" - ) - } - if (markerReceivedFromAllChannels) { - markerReceived.remove(markerId) // clean up if all markers are received - } - epochMarkerCompleted - } - - private def getChannelsWithinScope(marker: ChannelMarkerPayload): Set[ChannelIdentity] = { - if (marker.scope.isEmpty) inputGateway.getAllDataChannels.map(_.channelId) - else { - val upstreams = marker.scope.filter(_.toWorkerId == actorId) - inputGateway.getAllChannels - .map(_.channelId) - .filter { id => - upstreams.contains(id) - } - } - }.toSet -} diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/DPThread.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/DPThread.scala index 9ba4a693cca..28db348d15b 100644 --- a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/DPThread.scala +++ b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/DPThread.scala @@ -20,7 +20,7 @@ package edu.uci.ics.amber.engine.architecture.worker import edu.uci.ics.amber.engine.architecture.logreplay.ReplayLogManager -import edu.uci.ics.amber.engine.architecture.rpc.controlcommands.ChannelMarkerPayload +import edu.uci.ics.amber.engine.architecture.rpc.controlcommands.EmbeddedControlMessage import edu.uci.ics.amber.engine.architecture.worker.WorkflowWorker.{ DPInputQueueElement, MainThreadDelegateMessage @@ -198,8 +198,8 @@ class DPThread( dp.processControlPayload(msg.channelId, payload) case payload: DataPayload => dp.processDataPayload(msg.channelId, payload) - case payload: ChannelMarkerPayload => - dp.processChannelMarker(msg.channelId, payload, logManager) + case ecm: EmbeddedControlMessage => + dp.processECM(msg.channelId, ecm, logManager) } } } diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/DataProcessor.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/DataProcessor.scala index e30bfd3bb01..d0331bc5f5d 100644 --- a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/DataProcessor.scala +++ b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/DataProcessor.scala @@ -22,52 +22,24 @@ package edu.uci.ics.amber.engine.architecture.worker import com.softwaremill.macwire.wire import edu.uci.ics.amber.core.executor.OperatorExecutor import edu.uci.ics.amber.core.state.State -import edu.uci.ics.amber.core.tuple.{ - FinalizeExecutor, - FinalizePort, - SchemaEnforceable, - Tuple, - TupleLike -} +import edu.uci.ics.amber.core.tuple.{FinalizeExecutor, FinalizePort, SchemaEnforceable, Tuple, TupleLike} import edu.uci.ics.amber.engine.architecture.common.AmberProcessor import edu.uci.ics.amber.engine.architecture.logreplay.ReplayLogManager -import edu.uci.ics.amber.engine.architecture.messaginglayer.{ - InputManager, - OutputManager, - WorkerTimerService -} -import edu.uci.ics.amber.engine.architecture.rpc.controlcommands.ChannelMarkerType.{ - NO_ALIGNMENT, - PORT_ALIGNMENT, - ALL_ALIGNMENT -} +import edu.uci.ics.amber.engine.architecture.messaginglayer.{InputManager, OutputManager, WorkerTimerService} +import edu.uci.ics.amber.engine.architecture.rpc.controlcommands.EmbeddedControlMessageType.{NO_ALIGNMENT, PORT_ALIGNMENT} import edu.uci.ics.amber.engine.architecture.rpc.controlcommands._ -import edu.uci.ics.amber.engine.architecture.worker.WorkflowWorker.{ - DPInputQueueElement, - MainThreadDelegateMessage -} +import edu.uci.ics.amber.engine.architecture.worker.WorkflowWorker.{DPInputQueueElement, MainThreadDelegateMessage} import edu.uci.ics.amber.engine.architecture.worker.managers.SerializationManager -import edu.uci.ics.amber.engine.architecture.worker.statistics.WorkerState.{ - COMPLETED, - READY, - RUNNING -} +import edu.uci.ics.amber.engine.architecture.worker.statistics.WorkerState.{COMPLETED, READY, RUNNING} import edu.uci.ics.amber.engine.architecture.worker.statistics.WorkerStatistics import edu.uci.ics.amber.engine.common.ambermessage._ import edu.uci.ics.amber.engine.common.statetransition.WorkerStateManager import edu.uci.ics.amber.engine.common.virtualidentity.util.CONTROLLER import edu.uci.ics.amber.error.ErrorUtils.{mkConsoleMessage, safely} -import edu.uci.ics.amber.core.virtualidentity.{ - ActorVirtualIdentity, - ChannelIdentity, - ChannelMarkerIdentity -} +import edu.uci.ics.amber.core.virtualidentity.{ActorVirtualIdentity, ChannelIdentity, EmbeddedControlMessageIdentity} import edu.uci.ics.amber.core.workflow.PortIdentity import edu.uci.ics.amber.engine.architecture.rpc.controlreturns.EmptyReturn -import edu.uci.ics.amber.engine.architecture.rpc.workerservice.WorkerServiceGrpc.{ - METHOD_END_CHANNEL, - METHOD_START_CHANNEL -} +import edu.uci.ics.amber.engine.architecture.rpc.workerservice.WorkerServiceGrpc.METHOD_END_CHANNEL import io.grpc.MethodDescriptor import java.util.concurrent.LinkedBlockingQueue @@ -93,8 +65,8 @@ class DataProcessor( val stateManager: WorkerStateManager = new WorkerStateManager(actorId) val inputManager: InputManager = new InputManager(actorId, inputMessageQueue) val outputManager: OutputManager = new OutputManager(actorId, outputGateway) - val channelMarkerManager: ChannelMarkerManager = - new ChannelMarkerManager(actorId, inputGateway, inputManager) + val ecmManager: EmbeddedControlMessageManager = + new EmbeddedControlMessageManager(actorId, inputGateway, inputManager) val serializationManager: SerializationManager = new SerializationManager(actorId) def getQueuedCredit(channelId: ChannelIdentity): Long = { @@ -234,19 +206,19 @@ class DataProcessor( statisticsManager.increaseDataProcessingTime(System.nanoTime() - dataProcessingStartTime) } - def processChannelMarker( + def processECM( channelId: ChannelIdentity, - marker: ChannelMarkerPayload, + marker: EmbeddedControlMessage, logManager: ReplayLogManager ): Unit = { inputManager.currentChannelId = channelId val markerId = marker.id val command = marker.commandMapping.get(actorId.name) logger.info(s"receive marker from $channelId, id = $markerId, cmd = $command") - if (marker.markerType != NO_ALIGNMENT) { + if (marker.ecmType != NO_ALIGNMENT) { pauseManager.pauseInputChannel(EpochMarkerPause(markerId), List(channelId)) } - if (channelMarkerManager.isMarkerAligned(channelId, marker)) { + if (ecmManager.isMarkerAligned(channelId, marker)) { logManager.markAsReplayDestination(markerId) // invoke the control command carried with the epoch marker logger.info(s"process marker from $channelId, id = $markerId, cmd = $command") @@ -267,7 +239,7 @@ class DataProcessor( } } // unblock input channels - if (marker.markerType != NO_ALIGNMENT) { + if (marker.ecmType != NO_ALIGNMENT) { pauseManager.resume(EpochMarkerPause(markerId)) } } @@ -275,14 +247,14 @@ class DataProcessor( def sendChannelMarkerToDataChannels( method: MethodDescriptor[EmptyRequest, EmptyReturn], - alignment: ChannelMarkerType + alignment: EmbeddedControlMessageType ): Unit = { outputManager.flush() outputGateway.getActiveChannels .filter(!_.isControl) .foreach { activeChannelId => - asyncRPCClient.sendChannelMarker( - ChannelMarkerIdentity(method.getBareMethodName), + asyncRPCClient.sendECM( + EmbeddedControlMessageIdentity(method.getBareMethodName), alignment, Set(), Map( diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/EmbeddedControlMessageManager.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/EmbeddedControlMessageManager.scala new file mode 100644 index 00000000000..1fcb22a2ba4 --- /dev/null +++ b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/EmbeddedControlMessageManager.scala @@ -0,0 +1,98 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package edu.uci.ics.amber.engine.architecture.worker + +import edu.uci.ics.amber.engine.architecture.messaginglayer.{InputGateway, InputManager} +import edu.uci.ics.amber.engine.architecture.rpc.controlcommands.EmbeddedControlMessage +import edu.uci.ics.amber.engine.architecture.rpc.controlcommands.EmbeddedControlMessageType.{ + NO_ALIGNMENT, + PORT_ALIGNMENT, + ALL_ALIGNMENT +} +import edu.uci.ics.amber.engine.common.{AmberLogging, CheckpointState} +import edu.uci.ics.amber.core.virtualidentity.{ + ActorVirtualIdentity, + ChannelIdentity, + EmbeddedControlMessageIdentity +} + +import scala.collection.mutable + +class EmbeddedControlMessageManager( + val actorId: ActorVirtualIdentity, + inputGateway: InputGateway, + inputManager: InputManager +) extends AmberLogging { + + private val ecmReceived = new mutable.HashMap[EmbeddedControlMessageIdentity, Set[ChannelIdentity]]() + + val checkpoints = new mutable.HashMap[EmbeddedControlMessageIdentity, CheckpointState]() + + /** + * Determines if an epoch ECM is fully received from all relevant senders within its scope. + * This method checks if the ECM, based on its type, has been received from all necessary channels. + * For ECMs requiring alignment, it verifies receipt from all senders in the scope. For non-aligned ECMs, + * it checks if it's the first received ECM. Post verification, it cleans up the ECMs. + * + * @return Boolean indicating if the ECM is completely received from all senders + * within the scope. Returns true if the ECM is aligned, otherwise false. + */ + def isMarkerAligned( + from: ChannelIdentity, + ecm: EmbeddedControlMessage + ): Boolean = { + val portId = inputGateway.getChannel(from).getPortId + if (!ecmReceived.contains(ecm.id)) { + ecmReceived(ecm.id) = Set() + } + ecmReceived.update(ecm.id, ecmReceived(ecm.id) + from) + val ecmReceivedFromAllChannels = + getChannelsWithinScope(ecm).subsetOf(ecmReceived(ecm.id)) + // check if the ECM is completed + val ecmCompleted = ecm.ecmType match { + case ALL_ALIGNMENT => + ecmReceivedFromAllChannels + case PORT_ALIGNMENT => + inputManager.getPort(portId).channels.subsetOf(ecmReceived(ecm.id)) + case NO_ALIGNMENT => + ecmReceived(ecm.id).size == 1 // only the first ECM triggers + case _ => + throw new IllegalArgumentException( + s"Unsupported ECM type: ${ecm.ecmType}" + ) + } + if (ecmReceivedFromAllChannels) { + ecmReceived.remove(ecm.id) // clean up if all ECMs are received + } + ecmCompleted + } + + private def getChannelsWithinScope(ecm: EmbeddedControlMessage): Set[ChannelIdentity] = { + if (ecm.scope.isEmpty) inputGateway.getAllDataChannels.map(_.channelId) + else { + val upstreams = ecm.scope.filter(_.toWorkerId == actorId) + inputGateway.getAllChannels + .map(_.channelId) + .filter { id => + upstreams.contains(id) + } + } + }.toSet +} diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/PauseType.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/PauseType.scala index 59c874721e7..770a6bcb1b0 100644 --- a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/PauseType.scala +++ b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/PauseType.scala @@ -19,7 +19,7 @@ package edu.uci.ics.amber.engine.architecture.worker -import edu.uci.ics.amber.core.virtualidentity.ChannelMarkerIdentity +import edu.uci.ics.amber.core.virtualidentity.EmbeddedControlMessageIdentity sealed trait PauseType @@ -31,4 +31,4 @@ object OperatorLogicPause extends PauseType object SchedulerTimeSlotExpiredPause extends PauseType -case class EpochMarkerPause(id: ChannelMarkerIdentity) extends PauseType +case class EpochMarkerPause(id: EmbeddedControlMessageIdentity) extends PauseType diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/WorkflowWorker.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/WorkflowWorker.scala index 14668c67508..5a99f693c4c 100644 --- a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/WorkflowWorker.scala +++ b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/WorkflowWorker.scala @@ -31,7 +31,7 @@ import edu.uci.ics.amber.engine.common.actormessage.{ActorCommand, Backpressure} import edu.uci.ics.amber.engine.common.ambermessage.WorkflowFIFOMessage import edu.uci.ics.amber.engine.common.ambermessage.WorkflowMessage.getInMemSize import edu.uci.ics.amber.engine.common.{CheckpointState, SerializedState} -import edu.uci.ics.amber.core.virtualidentity.{ChannelIdentity, ChannelMarkerIdentity} +import edu.uci.ics.amber.core.virtualidentity.{ChannelIdentity, EmbeddedControlMessageIdentity} import java.net.URI import java.util.concurrent.LinkedBlockingQueue @@ -66,7 +66,7 @@ object WorkflowWorker { faultToleranceConfOpt: Option[FaultToleranceConfig] = None ) - final case class StateRestoreConfig(readFrom: URI, replayDestination: ChannelMarkerIdentity) + final case class StateRestoreConfig(readFrom: URI, replayDestination: EmbeddedControlMessageIdentity) final case class FaultToleranceConfig(writeTo: URI) } @@ -85,7 +85,7 @@ class WorkflowWorker( var dpThread: DPThread = _ val recordedInputs = - new mutable.HashMap[ChannelMarkerIdentity, mutable.ArrayBuffer[WorkflowFIFOMessage]]() + new mutable.HashMap[EmbeddedControlMessageIdentity, mutable.ArrayBuffer[WorkflowFIFOMessage]]() override def initState(): Unit = { dp.initTimerService(timerService) diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/managers/InputPortMaterializationReaderThread.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/managers/InputPortMaterializationReaderThread.scala index 8e08267f3ff..f6bdaf8476d 100644 --- a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/managers/InputPortMaterializationReaderThread.scala +++ b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/managers/InputPortMaterializationReaderThread.scala @@ -26,17 +26,17 @@ import edu.uci.ics.amber.core.tuple.Tuple import edu.uci.ics.amber.core.virtualidentity.{ ActorVirtualIdentity, ChannelIdentity, - ChannelMarkerIdentity + EmbeddedControlMessageIdentity } import edu.uci.ics.amber.engine.architecture.messaginglayer.OutputManager.toPartitioner -import edu.uci.ics.amber.engine.architecture.rpc.controlcommands.ChannelMarkerType.{ +import edu.uci.ics.amber.engine.architecture.rpc.controlcommands.EmbeddedControlMessageType.{ NO_ALIGNMENT, PORT_ALIGNMENT } import edu.uci.ics.amber.engine.architecture.rpc.controlcommands.{ AsyncRPCContext, - ChannelMarkerPayload, - ChannelMarkerType, + EmbeddedControlMessage, + EmbeddedControlMessageType, ControlInvocation, EmptyRequest } @@ -88,7 +88,7 @@ class InputPortMaterializationReaderThread( */ override def run(): Unit = { // Notify the input port of start of input channel - emitChannelMarker(METHOD_START_CHANNEL, NO_ALIGNMENT) + emitECM(METHOD_START_CHANNEL, NO_ALIGNMENT) try { val materialization: VirtualDocument[Tuple] = DocumentFactory .openDocument(uri) @@ -112,7 +112,7 @@ class InputPortMaterializationReaderThread( } // Flush any remaining tuples in the buffer. if (buffer.nonEmpty) flush() - emitChannelMarker(METHOD_END_CHANNEL, PORT_ALIGNMENT) + emitECM(METHOD_END_CHANNEL, PORT_ALIGNMENT) isFinished.set(true) } catch { case e: Exception => @@ -121,15 +121,15 @@ class InputPortMaterializationReaderThread( } /** - * Puts a channel marker into the internal queue. + * Puts a ECM into the internal queue. */ - private def emitChannelMarker( + private def emitECM( method: MethodDescriptor[EmptyRequest, EmptyReturn], - alignment: ChannelMarkerType + alignment: EmbeddedControlMessageType ): Unit = { flush() - val markerPayload = ChannelMarkerPayload( - ChannelMarkerIdentity(method.getBareMethodName), + val ecm = EmbeddedControlMessage( + EmbeddedControlMessageIdentity(method.getBareMethodName), alignment, Seq(), Map( @@ -142,7 +142,7 @@ class InputPortMaterializationReaderThread( ) ) ) - val fifoMessage = WorkflowFIFOMessage(channelId, getSequenceNumber, markerPayload) + val fifoMessage = WorkflowFIFOMessage(channelId, getSequenceNumber, ecm) val inputQueueElement = FIFOMessageElement(fifoMessage) inputMessageQueue.put(inputQueueElement) } diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/FinalizeCheckpointHandler.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/FinalizeCheckpointHandler.scala index b589a94b21a..bc8f0e4171a 100644 --- a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/FinalizeCheckpointHandler.scala +++ b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/FinalizeCheckpointHandler.scala @@ -44,9 +44,9 @@ trait FinalizeCheckpointHandler { msg: FinalizeCheckpointRequest, ctx: AsyncRPCContext ): Future[FinalizeCheckpointResponse] = { - val checkpointSize = if (dp.channelMarkerManager.checkpoints.contains(msg.checkpointId)) { + val checkpointSize = if (dp.ecmManager.checkpoints.contains(msg.checkpointId)) { val waitFuture = new CompletableFuture[Unit]() - val chkpt = dp.channelMarkerManager.checkpoints(msg.checkpointId) + val chkpt = dp.ecmManager.checkpoints(msg.checkpointId) val closure = (worker: WorkflowWorker) => { logger.info(s"Main thread: start to serialize recorded messages.") chkpt.save( diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/PrepareCheckpointHandler.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/PrepareCheckpointHandler.scala index 3632b80f455..199ad8cfc1f 100644 --- a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/PrepareCheckpointHandler.scala +++ b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/PrepareCheckpointHandler.scala @@ -32,7 +32,7 @@ import edu.uci.ics.amber.engine.architecture.worker.{ } import edu.uci.ics.amber.engine.common.ambermessage.WorkflowFIFOMessage import edu.uci.ics.amber.engine.common.{CheckpointState, CheckpointSupport, SerializedState} -import edu.uci.ics.amber.core.virtualidentity.ChannelMarkerIdentity +import edu.uci.ics.amber.core.virtualidentity.EmbeddedControlMessageIdentity import java.util.concurrent.CompletableFuture import scala.collection.mutable @@ -55,12 +55,12 @@ trait PrepareCheckpointHandler { EmptyReturn() } - private def serializeWorkerState(checkpointId: ChannelMarkerIdentity): Unit = { + private def serializeWorkerState(checkpointId: EmbeddedControlMessageIdentity): Unit = { val chkpt = new CheckpointState() // 1. serialize DP state chkpt.save(SerializedState.DP_STATE_KEY, this.dp) // checkpoint itself should not be serialized, thus we register it after serialization - dp.channelMarkerManager.checkpoints(checkpointId) = chkpt + dp.ecmManager.checkpoints(checkpointId) = chkpt logger.info("Serialized DP state") // 2. serialize operator state dp.executor match { diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/StartChannelHandler.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/StartChannelHandler.scala index e1dcd09e66f..1bbd9eec257 100644 --- a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/StartChannelHandler.scala +++ b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/StartChannelHandler.scala @@ -20,7 +20,7 @@ package edu.uci.ics.amber.engine.architecture.worker.promisehandlers import com.twitter.util.Future -import edu.uci.ics.amber.engine.architecture.rpc.controlcommands.ChannelMarkerType.NO_ALIGNMENT +import edu.uci.ics.amber.engine.architecture.rpc.controlcommands.EmbeddedControlMessageType.NO_ALIGNMENT import edu.uci.ics.amber.engine.architecture.rpc.controlcommands.{AsyncRPCContext, EmptyRequest} import edu.uci.ics.amber.engine.architecture.rpc.controlreturns.EmptyReturn import edu.uci.ics.amber.engine.architecture.rpc.workerservice.WorkerServiceGrpc.METHOD_START_CHANNEL diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/common/rpc/AsyncRPCClient.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/common/rpc/AsyncRPCClient.scala index 283f6e2bf21..a7e293cbb6f 100644 --- a/core/amber/src/main/scala/edu/uci/ics/amber/engine/common/rpc/AsyncRPCClient.scala +++ b/core/amber/src/main/scala/edu/uci/ics/amber/engine/common/rpc/AsyncRPCClient.scala @@ -38,7 +38,7 @@ import edu.uci.ics.amber.error.ErrorUtils.reconstructThrowable import edu.uci.ics.amber.core.virtualidentity.{ ActorVirtualIdentity, ChannelIdentity, - ChannelMarkerIdentity + EmbeddedControlMessageIdentity } import io.grpc.MethodDescriptor @@ -154,17 +154,17 @@ class AsyncRPCClient( (ControlInvocation(methodName, message, context, pid), p) } - def sendChannelMarker( - markerId: ChannelMarkerIdentity, - markerType: ChannelMarkerType, + def sendECM( + ecmId: EmbeddedControlMessageIdentity, + ecmType: EmbeddedControlMessageType, scope: Set[ChannelIdentity], cmdMapping: Map[String, ControlInvocation], channelId: ChannelIdentity ): Unit = { - logger.debug(s"send marker: $markerId to $channelId") + logger.debug(s"send ECM: $ecmId to $channelId") outputGateway.sendTo( channelId, - ChannelMarkerPayload(markerId, markerType, scope.toSeq, cmdMapping) + EmbeddedControlMessage(ecmId, ecmType, scope.toSeq, cmdMapping) ) } diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/common/rpc/AsyncRPCHandlerInitializer.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/common/rpc/AsyncRPCHandlerInitializer.scala index 27dd1d1499f..5d62a3f911f 100644 --- a/core/amber/src/main/scala/edu/uci/ics/amber/engine/common/rpc/AsyncRPCHandlerInitializer.scala +++ b/core/amber/src/main/scala/edu/uci/ics/amber/engine/common/rpc/AsyncRPCHandlerInitializer.scala @@ -28,7 +28,7 @@ import edu.uci.ics.amber.engine.architecture.rpc.workerservice.WorkerServiceFs2G import edu.uci.ics.amber.core.virtualidentity.{ ActorVirtualIdentity, ChannelIdentity, - ChannelMarkerIdentity + EmbeddedControlMessageIdentity } import scala.language.implicitConversions @@ -55,14 +55,14 @@ class AsyncRPCHandlerInitializer( def mkContext(to: ActorVirtualIdentity): AsyncRPCContext = ctrlSource.mkContext(to) - def sendChannelMarker( - markerId: ChannelMarkerIdentity, - markerType: ChannelMarkerType, + def sendECM( + ecmId: EmbeddedControlMessageIdentity, + ecmType: EmbeddedControlMessageType, scope: Set[ChannelIdentity], cmdMapping: Map[String, ControlInvocation], to: ChannelIdentity ): Unit = { - ctrlSource.sendChannelMarker(markerId, markerType, scope, cmdMapping, to) + ctrlSource.sendECM(ecmId, ecmType, scope, cmdMapping, to) } def sendToClient(clientEvent: ClientEvent): Unit = { diff --git a/core/amber/src/main/scala/edu/uci/ics/texera/web/resource/dashboard/user/workflow/WorkflowExecutionsResource.scala b/core/amber/src/main/scala/edu/uci/ics/texera/web/resource/dashboard/user/workflow/WorkflowExecutionsResource.scala index 5179ef0aa54..4530133a60f 100644 --- a/core/amber/src/main/scala/edu/uci/ics/texera/web/resource/dashboard/user/workflow/WorkflowExecutionsResource.scala +++ b/core/amber/src/main/scala/edu/uci/ics/texera/web/resource/dashboard/user/workflow/WorkflowExecutionsResource.scala @@ -553,7 +553,7 @@ class WorkflowExecutionsResource { if (logLocation != null && logLocation.nonEmpty) { val storage = SequentialRecordStorage.getStorage[ReplayLogRecord](Some(new URI(logLocation))) - val result = new mutable.ArrayBuffer[ChannelMarkerIdentity]() + val result = new mutable.ArrayBuffer[EmbeddedControlMessageIdentity]() storage.getReader("CONTROLLER").mkRecordIterator().foreach { case destination: ReplayDestination => result.append(destination.id) diff --git a/core/amber/src/main/scala/edu/uci/ics/texera/web/service/ExecutionRuntimeService.scala b/core/amber/src/main/scala/edu/uci/ics/texera/web/service/ExecutionRuntimeService.scala index b5ed2835699..a634d921e0f 100644 --- a/core/amber/src/main/scala/edu/uci/ics/texera/web/service/ExecutionRuntimeService.scala +++ b/core/amber/src/main/scala/edu/uci/ics/texera/web/service/ExecutionRuntimeService.scala @@ -28,7 +28,7 @@ import edu.uci.ics.amber.engine.architecture.rpc.controlcommands.{ import edu.uci.ics.amber.engine.architecture.rpc.controlreturns.WorkflowAggregatedState._ import edu.uci.ics.amber.engine.architecture.worker.WorkflowWorker.FaultToleranceConfig import edu.uci.ics.amber.engine.common.client.AmberClient -import edu.uci.ics.amber.core.virtualidentity.ChannelMarkerIdentity +import edu.uci.ics.amber.core.virtualidentity.EmbeddedControlMessageIdentity import edu.uci.ics.texera.web.model.websocket.request._ import edu.uci.ics.texera.web.storage.ExecutionStateStore import edu.uci.ics.texera.web.storage.ExecutionStateStore.updateWorkflowState @@ -118,7 +118,7 @@ class ExecutionRuntimeService( logConf.nonEmpty, "Fault tolerance log folder is not established. Unable to take a global checkpoint." ) - val checkpointId = ChannelMarkerIdentity(s"Checkpoint_${UUID.randomUUID().toString}") + val checkpointId = EmbeddedControlMessageIdentity(s"Checkpoint_${UUID.randomUUID().toString}") val uri = logConf.get.writeTo.resolve(checkpointId.toString) client.controllerInterface.takeGlobalCheckpoint( TakeGlobalCheckpointRequest(estimationOnly = false, checkpointId, uri.toString), diff --git a/core/amber/src/main/scala/edu/uci/ics/texera/web/service/FriesReconfigurationAlgorithm.scala b/core/amber/src/main/scala/edu/uci/ics/texera/web/service/FriesReconfigurationAlgorithm.scala index 01b3f22f3b9..c93af5d6caf 100644 --- a/core/amber/src/main/scala/edu/uci/ics/texera/web/service/FriesReconfigurationAlgorithm.scala +++ b/core/amber/src/main/scala/edu/uci/ics/texera/web/service/FriesReconfigurationAlgorithm.scala @@ -22,7 +22,7 @@ package edu.uci.ics.texera.web.service import edu.uci.ics.amber.core.workflow.PhysicalPlan import edu.uci.ics.amber.engine.architecture.rpc.controlcommands.{ ModifyLogicRequest, - PropagateChannelMarkerRequest + PropagateEmbeddedControlMessageRequest } import edu.uci.ics.amber.engine.architecture.scheduling.{Region, WorkflowExecutionCoordinator} import edu.uci.ics.amber.core.virtualidentity.PhysicalOpIdentity @@ -42,7 +42,7 @@ object FriesReconfigurationAlgorithm { workflowExecutionCoordinator: WorkflowExecutionCoordinator, reconfiguration: ModifyLogicRequest, epochMarkerId: String - ): Set[PropagateChannelMarkerRequest] = { + ): Set[PropagateEmbeddedControlMessageRequest] = { // independently schedule reconfigurations for each region: workflowExecutionCoordinator.getExecutingRegions .flatMap(region => computeMCS(region, reconfiguration, epochMarkerId)) @@ -52,7 +52,7 @@ object FriesReconfigurationAlgorithm { region: Region, reconfiguration: ModifyLogicRequest, epochMarkerId: String - ): List[PropagateChannelMarkerRequest] = { + ): List[PropagateEmbeddedControlMessageRequest] = { // add all reconfiguration operators to M val reconfigOps = reconfiguration.updateRequest.map(req => req.targetOpId).toSet @@ -101,7 +101,7 @@ object FriesReconfigurationAlgorithm { // find the MCS components, // for each component, send an epoch marker to each of its source operators - val epochMarkers = new ArrayBuffer[PropagateChannelMarkerRequest]() + val epochMarkers = new ArrayBuffer[PropagateEmbeddedControlMessageRequest]() val connectedSets = new ConnectivityInspector(mcsPlan.dag).connectedSets() connectedSets.forEach(component => { @@ -116,9 +116,9 @@ object FriesReconfigurationAlgorithm { // // // find the source operators of the component // val sources = componentSet.intersect(mcsPlan.getSourceOperatorIds) - // epochMarkers += PropagateChannelMarkerRequest( + // epochMarkers += PropagateEmbeddedControlMessageRequest( // sources.toSeq, - // ChannelMarkerIdentity(epochMarkerId), + // EmbeddedControlMessageIdentity(epochMarkerId), // ALL_ALIGNMENT, // componentPlan.operators.map(_.id).toSeq, // reconfigTargets, diff --git a/core/amber/src/main/scala/edu/uci/ics/texera/web/service/WorkflowService.scala b/core/amber/src/main/scala/edu/uci/ics/texera/web/service/WorkflowService.scala index adbb9d195dc..1fee982fde9 100644 --- a/core/amber/src/main/scala/edu/uci/ics/texera/web/service/WorkflowService.scala +++ b/core/amber/src/main/scala/edu/uci/ics/texera/web/service/WorkflowService.scala @@ -36,7 +36,7 @@ import edu.uci.ics.amber.engine.architecture.worker.WorkflowWorker.{ } import edu.uci.ics.amber.error.ErrorUtils.{getOperatorFromActorIdOpt, getStackTraceWithAllCauses} import edu.uci.ics.amber.core.virtualidentity.{ - ChannelMarkerIdentity, + EmbeddedControlMessageIdentity, ExecutionIdentity, WorkflowIdentity } @@ -230,7 +230,7 @@ class WorkflowService( Some( StateRestoreConfig( readFrom = readLocation, - replayDestination = ChannelMarkerIdentity(replayInfo.interaction) + replayDestination = EmbeddedControlMessageIdentity(replayInfo.interaction) ) ) ) diff --git a/core/amber/src/test/scala/edu/uci/ics/amber/engine/architecture/worker/DataProcessorSpec.scala b/core/amber/src/test/scala/edu/uci/ics/amber/engine/architecture/worker/DataProcessorSpec.scala index 547c4f74b37..700d73efb5d 100644 --- a/core/amber/src/test/scala/edu/uci/ics/amber/engine/architecture/worker/DataProcessorSpec.scala +++ b/core/amber/src/test/scala/edu/uci/ics/amber/engine/architecture/worker/DataProcessorSpec.scala @@ -25,8 +25,8 @@ import edu.uci.ics.amber.core.workflow.WorkflowContext.DEFAULT_WORKFLOW_ID import edu.uci.ics.amber.engine.architecture.messaginglayer.WorkerTimerService import edu.uci.ics.amber.engine.architecture.rpc.controlcommands.{ AsyncRPCContext, - ChannelMarkerPayload, - ChannelMarkerType, + EmbeddedControlMessage, + EmbeddedControlMessageType, EmptyRequest } import edu.uci.ics.amber.engine.architecture.rpc.workerservice.WorkerServiceGrpc.{ @@ -46,7 +46,7 @@ import edu.uci.ics.amber.util.VirtualIdentityUtils import edu.uci.ics.amber.core.virtualidentity.{ ActorVirtualIdentity, ChannelIdentity, - ChannelMarkerIdentity, + EmbeddedControlMessageIdentity, OperatorIdentity, PhysicalOpIdentity } @@ -85,9 +85,9 @@ class DataProcessorSpec extends AnyFlatSpec with MockFactory with BeforeAndAfter private val logStorage = SequentialRecordStorage.getStorage[ReplayLogRecord](None) private val logManager: ReplayLogManager = ReplayLogManager.createLogManager(logStorage, "none", x => {}) - private val endChannelPayload = ChannelMarkerPayload( - ChannelMarkerIdentity("EndChannel"), - ChannelMarkerType.PORT_ALIGNMENT, + private val endChannelPayload = EmbeddedControlMessage( + EmbeddedControlMessageIdentity("EndChannel"), + EmbeddedControlMessageType.PORT_ALIGNMENT, Seq(), Map( testWorkerId.name -> @@ -165,7 +165,7 @@ class DataProcessorSpec extends AnyFlatSpec with MockFactory with BeforeAndAfter while (dp.inputManager.hasUnfinishedInput || dp.outputManager.hasUnfinishedOutput) { dp.continueDataProcessing() } - dp.processChannelMarker( + dp.processECM( ChannelIdentity(senderWorkerId, testWorkerId, isControl = false), endChannelPayload, logManager @@ -237,7 +237,7 @@ class DataProcessorSpec extends AnyFlatSpec with MockFactory with BeforeAndAfter } (adaptiveBatchingMonitor.stopAdaptiveBatching _).expects().once() (executor.close _).expects().once() - dp.processChannelMarker( + dp.processECM( ChannelIdentity(senderWorkerId, testWorkerId, isControl = false), endChannelPayload, logManager diff --git a/core/amber/src/test/scala/edu/uci/ics/amber/engine/faulttolerance/CheckpointSpec.scala b/core/amber/src/test/scala/edu/uci/ics/amber/engine/faulttolerance/CheckpointSpec.scala index db64587e658..daabe6a9947 100644 --- a/core/amber/src/test/scala/edu/uci/ics/amber/engine/faulttolerance/CheckpointSpec.scala +++ b/core/amber/src/test/scala/edu/uci/ics/amber/engine/faulttolerance/CheckpointSpec.scala @@ -117,7 +117,7 @@ class CheckpointSpec extends AnyFlatSpecLike with BeforeAndAfterAll { // Await.result(client1.controllerInterface.startWorkflow(EmptyRequest(), ())) // Thread.sleep(100) // Await.result(client1.controllerInterface.pauseWorkflow(EmptyRequest(), ())) -// val checkpointId = ChannelMarkerIdentity(s"Checkpoint_test_1") +// val checkpointId = EmbeddedControlMessageIdentity(s"Checkpoint_test_1") // val uri = new URI("ram:///recovery-logs/tmp/") // Await.result( // client1.controllerInterface.takeGlobalCheckpoint( diff --git a/core/gui/src/app/common/type/proto/edu/uci/ics/amber/engine/common/virtualidentity.ts b/core/gui/src/app/common/type/proto/edu/uci/ics/amber/engine/common/virtualidentity.ts index f522a308fb8..8ea9daeec3f 100644 --- a/core/gui/src/app/common/type/proto/edu/uci/ics/amber/engine/common/virtualidentity.ts +++ b/core/gui/src/app/common/type/proto/edu/uci/ics/amber/engine/common/virtualidentity.ts @@ -55,7 +55,7 @@ export interface PhysicalOpIdentity { layerName: string; } -export interface ChannelMarkerIdentity { +export interface EmbeddedControlMessageIdentity { id: string; } @@ -459,22 +459,22 @@ export const PhysicalOpIdentity: MessageFns = { }, }; -function createBaseChannelMarkerIdentity(): ChannelMarkerIdentity { +function createBaseEmbeddedControlMessageIdentity(): EmbeddedControlMessageIdentity { return { id: "" }; } -export const ChannelMarkerIdentity: MessageFns = { - encode(message: ChannelMarkerIdentity, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { +export const EmbeddedControlMessageIdentity: MessageFns = { + encode(message: EmbeddedControlMessageIdentity, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { if (message.id !== "") { writer.uint32(10).string(message.id); } return writer; }, - decode(input: BinaryReader | Uint8Array, length?: number): ChannelMarkerIdentity { + decode(input: BinaryReader | Uint8Array, length?: number): EmbeddedControlMessageIdentity { const reader = input instanceof BinaryReader ? input : new BinaryReader(input); let end = length === undefined ? reader.len : reader.pos + length; - const message = createBaseChannelMarkerIdentity(); + const message = createBaseEmbeddedControlMessageIdentity(); while (reader.pos < end) { const tag = reader.uint32(); switch (tag >>> 3) { @@ -494,11 +494,11 @@ export const ChannelMarkerIdentity: MessageFns = { return message; }, - fromJSON(object: any): ChannelMarkerIdentity { + fromJSON(object: any): EmbeddedControlMessageIdentity { return { id: isSet(object.id) ? globalThis.String(object.id) : "" }; }, - toJSON(message: ChannelMarkerIdentity): unknown { + toJSON(message: EmbeddedControlMessageIdentity): unknown { const obj: any = {}; if (message.id !== "") { obj.id = message.id; @@ -506,11 +506,11 @@ export const ChannelMarkerIdentity: MessageFns = { return obj; }, - create, I>>(base?: I): ChannelMarkerIdentity { - return ChannelMarkerIdentity.fromPartial(base ?? ({} as any)); + create, I>>(base?: I): EmbeddedControlMessageIdentity { + return EmbeddedControlMessageIdentity.fromPartial(base ?? ({} as any)); }, - fromPartial, I>>(object: I): ChannelMarkerIdentity { - const message = createBaseChannelMarkerIdentity(); + fromPartial, I>>(object: I): EmbeddedControlMessageIdentity { + const message = createBaseEmbeddedControlMessageIdentity(); message.id = object.id ?? ""; return message; }, diff --git a/core/scripts/gui-proto-gen.sh b/core/scripts/gui-proto-gen.sh index 5a3635a4e42..e3576a577e3 100755 --- a/core/scripts/gui-proto-gen.sh +++ b/core/scripts/gui-proto-gen.sh @@ -17,7 +17,7 @@ TEXERA_ROOT="$(git rev-parse --show-toplevel)" GUI_DIR="$TEXERA_ROOT/core/gui" -PROTOBUF_DIR="$TEXERA_ROOT/core/amber/src/main/protobuf" +PROTOBUF_DIR="$TEXERA_ROOT/core/workflow-core/src/main/protobuf" GUI_PROTO_DIR="$GUI_DIR/src/app/common/type" WORKFLOW_PROTO=$(find "$PROTOBUF_DIR" -iname "workflow.proto") diff --git a/core/workflow-core/src/main/protobuf/edu/uci/ics/amber/core/executor.proto b/core/workflow-core/src/main/protobuf/edu/uci/ics/amber/core/executor.proto index 358d0778ce9..2bfc26cb2c7 100644 --- a/core/workflow-core/src/main/protobuf/edu/uci/ics/amber/core/executor.proto +++ b/core/workflow-core/src/main/protobuf/edu/uci/ics/amber/core/executor.proto @@ -20,7 +20,6 @@ package edu.uci.ics.amber.core; import "edu/uci/ics/amber/core/virtualidentity.proto"; -import "edu/uci/ics/amber/core/workflow.proto"; import "scalapb/scalapb.proto"; option (scalapb.options) = { diff --git a/core/workflow-core/src/main/protobuf/edu/uci/ics/amber/core/virtualidentity.proto b/core/workflow-core/src/main/protobuf/edu/uci/ics/amber/core/virtualidentity.proto index 158bdfc21ad..8302b8a16bd 100644 --- a/core/workflow-core/src/main/protobuf/edu/uci/ics/amber/core/virtualidentity.proto +++ b/core/workflow-core/src/main/protobuf/edu/uci/ics/amber/core/virtualidentity.proto @@ -54,7 +54,7 @@ message PhysicalOpIdentity{ string layerName = 2; } -message ChannelMarkerIdentity{ +message EmbeddedControlMessageIdentity{ string id = 1; } From 0192bd5febeefba37163691b30da1a073493b57c Mon Sep 17 00:00:00 2001 From: Xinyuan Lin Date: Sat, 14 Jun 2025 04:53:06 -0700 Subject: [PATCH 02/39] update --- .../architecture/managers/pause_manager.py | 2 +- .../architecture/packaging/output_manager.py | 8 ++-- .../main/python/core/runnables/main_loop.py | 41 +++++++++---------- 3 files changed, 24 insertions(+), 27 deletions(-) diff --git a/core/amber/src/main/python/core/architecture/managers/pause_manager.py b/core/amber/src/main/python/core/architecture/managers/pause_manager.py index 3d0e8dd7086..7388a144c16 100644 --- a/core/amber/src/main/python/core/architecture/managers/pause_manager.py +++ b/core/amber/src/main/python/core/architecture/managers/pause_manager.py @@ -34,7 +34,7 @@ class PauseType(Enum): SCHEDULER_TIME_SLOT_EXPIRED_PAUSE = 2 DEBUG_PAUSE = 3 EXCEPTION_PAUSE = 4 - MARKER_PAUSE = 5 + ECM_PAUSE = 5 class PauseManager: diff --git a/core/amber/src/main/python/core/architecture/packaging/output_manager.py b/core/amber/src/main/python/core/architecture/packaging/output_manager.py index 16a2c264d2e..f685705b403 100644 --- a/core/amber/src/main/python/core/architecture/packaging/output_manager.py +++ b/core/amber/src/main/python/core/architecture/packaging/output_manager.py @@ -222,8 +222,8 @@ def tuple_to_batch( ) ) - def emit_channel_marker( - self, to: ActorVirtualIdentity, marker: EmbeddedControlMessage + def emit_ecm( + self, to: ActorVirtualIdentity, ecm: EmbeddedControlMessage ) -> Iterable[Union[DataPayload, EmbeddedControlMessage]]: return chain( *( @@ -233,7 +233,7 @@ def emit_channel_marker( if isinstance(payload, EmbeddedControlMessage) else self.tuple_to_frame(payload) ) - for payload in partitioner.flush(to, marker) + for payload in partitioner.flush(to, ecm) ) for partitioner in self._partitioners.values() ) @@ -253,7 +253,7 @@ def emit_state( else self.tuple_to_frame(payload) ), ) - for receiver, payload in partitioner.flush_marker(state) + for receiver, payload in partitioner.flush_state(state) ) for partitioner in self._partitioners.values() ) diff --git a/core/amber/src/main/python/core/runnables/main_loop.py b/core/amber/src/main/python/core/runnables/main_loop.py index 88767faaac5..d5ee3aab203 100644 --- a/core/amber/src/main/python/core/runnables/main_loop.py +++ b/core/amber/src/main/python/core/runnables/main_loop.py @@ -52,7 +52,7 @@ EmptyRequest, ConsoleMessageTriggeredRequest, EmbeddedControlMessageType, - EmbeddedControlMessagePayload, + EmbeddedControlMessage, AsyncRpcContext, ControlRequest, ) @@ -178,8 +178,7 @@ def process_input_tuple(self) -> None: Process the current input tuple with the current input link. Send all result Tuples or State to downstream workers. - This is being invoked for each Tuple/Marker that are unpacked from the - DataElement. + This is being invoked for each Tuple that are unpacked from the DataElement. """ if isinstance(self.context.tuple_processing_manager.current_input_tuple, Tuple): self.context.statistics_manager.increase_input_statistics( @@ -258,7 +257,7 @@ def _process_state(self, state_: State) -> None: self._check_and_process_control() def _process_start_channel(self) -> None: - self._send_channel_marker_to_data_channels( + self._send_ecm_to_data_channels( "StartChannel", EmbeddedControlMessageType.NO_ALIGNMENT ) self.process_input_state() @@ -286,7 +285,7 @@ def _process_end_channel(self) -> None: return self.context.output_manager.close_port_storage_writers() - self._send_channel_marker_to_data_channels( + self._send_ecm_to_data_channels( "EndChannel", EmbeddedControlMessageType.PORT_ALIGNMENT ) @@ -299,29 +298,27 @@ def _process_end_channel(self) -> None: def _process_ecm(self, ecm_element: EmbeddedControlMessageElement): """ - Processes a received channel marker payload and handles synchronization, + Processes a received ECM and handles synchronization, command execution, and forwarding to downstream channels if applicable. Args: - ecm_element (EmbeddedControlMessageElement): The received channel marker element. + ecm_element (EmbeddedControlMessageElement): The received ECM element. """ ecm = ecm_element.payload command = ecm.command_mapping.get(self.context.worker_id) channel_id = self.context.current_input_channel_id logger.info( - f"receive channel marker from {channel_id}," + f"receive channel ECM from {channel_id}," f" id = {ecm.id}, cmd = {command}" ) if ecm.ecm_type != EmbeddedControlMessageType.NO_ALIGNMENT: self.context.pause_manager.pause_input_channel( - PauseType.MARKER_PAUSE, channel_id + PauseType.ECM_PAUSE, channel_id ) - if self.context.ecm_manager.ecm_aligned( - channel_id, ecm - ): + if self.context.ecm_manager.ecm_aligned(channel_id, ecm): logger.info( - f"process channel marker from {channel_id}," + f"process channel ECM from {channel_id}," f" id = {ecm.id}, cmd = {command}" ) @@ -342,10 +339,10 @@ def _process_ecm(self, ecm_element: EmbeddedControlMessageElement): f"send marker to {active_channel_id}," f" id = {ecm.id}, cmd = {command}" ) - self._send_channel_marker(active_channel_id, ecm) + self._send_ecm(active_channel_id, ecm) if ecm.ecm_type != EmbeddedControlMessageType.NO_ALIGNMENT: - self.context.pause_manager.resume(PauseType.MARKER_PAUSE) + self.context.pause_manager.resume(PauseType.ECM_PAUSE) if self.context.tuple_processing_manager.current_internal_marker: { @@ -353,12 +350,12 @@ def _process_ecm(self, ecm_element: EmbeddedControlMessageElement): EndChannel: self._process_end_channel, }[type(self.context.tuple_processing_manager.current_internal_marker)]() - def _send_channel_marker_to_data_channels( + def _send_ecm_to_data_channels( self, method_name: str, alignment: EmbeddedControlMessageType ) -> None: for active_channel_id in self.context.output_manager.get_output_channel_ids(): if not active_channel_id.is_control: - marker_payload = EmbeddedControlMessagePayload( + ecm = EmbeddedControlMessage( EmbeddedControlMessageIdentity(method_name), alignment, [], @@ -373,13 +370,13 @@ def _send_channel_marker_to_data_channels( ) }, ) - self._send_channel_marker(active_channel_id, marker_payload) + self._send_ecm(active_channel_id, ecm) - def _send_channel_marker( - self, channel_id: ChannelIdentity, marker_payload: EmbeddedControlMessage + def _send_ecm( + self, channel_id: ChannelIdentity, ecm: EmbeddedControlMessage ) -> None: - for batch in self.context.output_manager.emit_channel_marker( - channel_id.to_worker_id, marker_payload + for batch in self.context.output_manager.emit_ecm( + channel_id.to_worker_id, ecm ): tag = channel_id element = ( From 86ecfb8e19e07125c5bd9b55ffd397a6984c9e22 Mon Sep 17 00:00:00 2001 From: Xinyuan Lin Date: Sat, 14 Jun 2025 05:14:07 -0700 Subject: [PATCH 03/39] update --- .../architecture/rpc/controlcommands.proto | 4 ++-- .../python/core/architecture/managers/context.py | 2 +- ...er.py => embedded_control_message_manager.py} | 2 +- .../src/main/python/core/runnables/main_loop.py | 4 ++-- .../python/core/runnables/network_receiver.py | 2 +- .../main/python/core/runnables/network_sender.py | 16 ++++++++-------- .../main/python/core/runnables/test_main_loop.py | 8 ++++---- .../core/runnables/test_network_receiver.py | 8 ++++---- ...input_port_materialization_reader_runnable.py | 16 ++++++++-------- .../amber/engine/architecture/rpc/__init__.py | 4 ++-- .../pythonworker/PythonProxyClient.scala | 2 +- .../pythonworker/PythonProxyServer.scala | 2 +- .../architecture/worker/DataProcessor.scala | 4 ++-- .../InputPortMaterializationReaderThread.scala | 2 +- .../promisehandlers/StartChannelHandler.scala | 2 +- 15 files changed, 39 insertions(+), 39 deletions(-) rename core/amber/src/main/python/core/architecture/managers/{channel_marker_manager.py => embedded_control_message_manager.py} (98%) diff --git a/core/amber/src/main/protobuf/edu/uci/ics/amber/engine/architecture/rpc/controlcommands.proto b/core/amber/src/main/protobuf/edu/uci/ics/amber/engine/architecture/rpc/controlcommands.proto index 8b5f3ccead7..51a95379574 100644 --- a/core/amber/src/main/protobuf/edu/uci/ics/amber/engine/architecture/rpc/controlcommands.proto +++ b/core/amber/src/main/protobuf/edu/uci/ics/amber/engine/architecture/rpc/controlcommands.proto @@ -108,8 +108,8 @@ message PropagateEmbeddedControlMessageRequest { EmbeddedControlMessageType ecm_type = 3; repeated core.PhysicalOpIdentity scope = 4; repeated core.PhysicalOpIdentity targetOps = 5; - ControlRequest markerCommand = 6; - string markerMethodName = 7; + ControlRequest command = 6; + string methodName = 7; } message TakeGlobalCheckpointRequest { diff --git a/core/amber/src/main/python/core/architecture/managers/context.py b/core/amber/src/main/python/core/architecture/managers/context.py index 964d5fccebe..2da589ada5f 100644 --- a/core/amber/src/main/python/core/architecture/managers/context.py +++ b/core/amber/src/main/python/core/architecture/managers/context.py @@ -19,7 +19,7 @@ from proto.edu.uci.ics.amber.engine.architecture.worker import WorkerState from typing import Optional from .console_message_manager import ConsoleMessageManager -from .channel_marker_manager import EmbeddedControlMessageManager +from .embedded_control_message_manager import EmbeddedControlMessageManager from .debug_manager import DebugManager from .exception_manager import ExceptionManager from .state_processing_manager import StateProcessingManager diff --git a/core/amber/src/main/python/core/architecture/managers/channel_marker_manager.py b/core/amber/src/main/python/core/architecture/managers/embedded_control_message_manager.py similarity index 98% rename from core/amber/src/main/python/core/architecture/managers/channel_marker_manager.py rename to core/amber/src/main/python/core/architecture/managers/embedded_control_message_manager.py index 387a0dfabaf..72752d8a169 100644 --- a/core/amber/src/main/python/core/architecture/managers/channel_marker_manager.py +++ b/core/amber/src/main/python/core/architecture/managers/embedded_control_message_manager.py @@ -36,7 +36,7 @@ def ecm_aligned( self, from_channel: ChannelIdentity, ecm: EmbeddedControlMessage ) -> bool: """ - Checks whether a ECM has been received from all expected + Checks whether an ECM has been received from all expected input channels, determining whether further processing can proceed. Args: diff --git a/core/amber/src/main/python/core/runnables/main_loop.py b/core/amber/src/main/python/core/runnables/main_loop.py index d5ee3aab203..140ff5d404f 100644 --- a/core/amber/src/main/python/core/runnables/main_loop.py +++ b/core/amber/src/main/python/core/runnables/main_loop.py @@ -336,7 +336,7 @@ def _process_ecm(self, ecm_element: EmbeddedControlMessageElement): ) in self.context.output_manager.get_output_channel_ids(): if active_channel_id in downstream_channels_in_scope: logger.info( - f"send marker to {active_channel_id}," + f"send ECM to {active_channel_id}," f" id = {ecm.id}, cmd = {command}" ) self._send_ecm(active_channel_id, ecm) @@ -388,7 +388,7 @@ def _send_ecm( def _process_data_element(self, data_element: DataElement) -> None: """ - Upon receipt of a DataElement, unpack it into Tuples and Markers, + Upon receipt of a DataElement, unpack it into Tuples and States, and process them one by one. :param data_element: DataElement, a batch of data. diff --git a/core/amber/src/main/python/core/runnables/network_receiver.py b/core/amber/src/main/python/core/runnables/network_receiver.py index 474d3386876..42c4b42966b 100644 --- a/core/amber/src/main/python/core/runnables/network_receiver.py +++ b/core/amber/src/main/python/core/runnables/network_receiver.py @@ -97,7 +97,7 @@ def data_handler(command: bytes, table: Table) -> int: lambda _: DataFrame(table), "State", lambda _: StateFrame(State(table)), - "ChannelMarker", + "ECM", lambda _: EmbeddedControlMessage().parse(table["payload"][0].as_py()), ) if isinstance(payload, EmbeddedControlMessage): diff --git a/core/amber/src/main/python/core/runnables/network_sender.py b/core/amber/src/main/python/core/runnables/network_sender.py index ca536bc5388..b29dae3e85b 100644 --- a/core/amber/src/main/python/core/runnables/network_sender.py +++ b/core/amber/src/main/python/core/runnables/network_sender.py @@ -63,27 +63,27 @@ def receive(self, next_entry: InternalQueueElement): elif isinstance(next_entry, ControlElement): self._send_control(next_entry.tag, next_entry.payload) elif isinstance(next_entry, EmbeddedControlMessageElement): - self._send_channel_marker(next_entry.tag, next_entry.payload) + self._send_ecm(next_entry.tag, next_entry.payload) else: raise TypeError(f"Unexpected entry {next_entry}") @logger.catch(reraise=True) - def _send_channel_marker( - self, to: ChannelIdentity, data_payload: EmbeddedControlMessage + def _send_ecm( + self, to: ChannelIdentity, ecm: EmbeddedControlMessage ) -> None: """ - Sends a channel marker payload to the specified channel. + Sends an ECM to the specified channel. Args: - to (ChannelIdentity): The target channel to which the marker should be sent. - data_payload (EmbeddedControlMessage): The channel marker payload to send. + to (ChannelIdentity): The target channel to which the ECM should be sent. + ecm (EmbeddedControlMessage): The ECM to send. This function constructs a `PythonDataHeader` with the appropriate metadata, serializes the payload into an Arrow table, and sends it using the proxy client. """ - data_header = PythonDataHeader(tag=to, payload_type="ChannelMarker") + data_header = PythonDataHeader(tag=to, payload_type="ECM") schema = pa.schema([("payload", pa.binary())]) - data = [pa.array([bytes(data_payload)])] + data = [pa.array([bytes(ecm)])] table = pa.Table.from_arrays(data, schema=schema) self._proxy_client.send_data(bytes(data_header), table) diff --git a/core/amber/src/main/python/core/runnables/test_main_loop.py b/core/amber/src/main/python/core/runnables/test_main_loop.py index 973541a78a3..2d65e6d195b 100644 --- a/core/amber/src/main/python/core/runnables/test_main_loop.py +++ b/core/amber/src/main/python/core/runnables/test_main_loop.py @@ -1165,15 +1165,15 @@ def test_main_loop_thread_can_align_ecm( "NoOperation", EmptyRequest(), AsyncRpcContext(), 98 ) } - test_marker = EmbeddedControlMessage( - "test_marker", EmbeddedControlMessageType.ALL_ALIGNMENT, scope, command_mapping + test_ecm = EmbeddedControlMessage( + "test_ecm", EmbeddedControlMessageType.ALL_ALIGNMENT, scope, command_mapping ) input_queue.put( - EmbeddedControlMessageElement(tag=mock_control_input_channel, payload=test_marker) + EmbeddedControlMessageElement(tag=mock_control_input_channel, payload=test_ecm) ) input_queue.put(mock_binary_data_element) input_queue.put( - EmbeddedControlMessageElement(tag=mock_data_input_channel, payload=test_marker) + EmbeddedControlMessageElement(tag=mock_data_input_channel, payload=test_ecm) ) output_data_element: DataElement = output_queue.get() assert output_data_element.tag == mock_data_output_channel diff --git a/core/amber/src/main/python/core/runnables/test_network_receiver.py b/core/amber/src/main/python/core/runnables/test_network_receiver.py index 8920b9c4731..5e66d23b23b 100644 --- a/core/amber/src/main/python/core/runnables/test_network_receiver.py +++ b/core/amber/src/main/python/core/runnables/test_network_receiver.py @@ -159,7 +159,7 @@ def test_network_receiver_can_receive_control_messages( assert element.tag == channel_id @pytest.mark.timeout(10) - def test_network_receiver_can_receive_channel_marker( + def test_network_receiver_can_receive_ecm( self, output_queue, input_queue, @@ -169,7 +169,7 @@ def test_network_receiver_can_receive_channel_marker( network_sender_thread.start() worker_id = ActorVirtualIdentity(name="test") channel_id = ChannelIdentity(worker_id, worker_id, False) - marker_id = EmbeddedControlMessageIdentity("test_marker") + ecm_id = EmbeddedControlMessageIdentity("test_ecm") scope = [channel_id] rpc_context = AsyncRpcContext(worker_id, worker_id) command_mapping = { @@ -184,7 +184,7 @@ def test_network_receiver_can_receive_channel_marker( EmbeddedControlMessageElement( tag=channel_id, payload=EmbeddedControlMessage( - marker_id, + ecm_id, EmbeddedControlMessageType.ALL_ALIGNMENT, scope, command_mapping, @@ -194,7 +194,7 @@ def test_network_receiver_can_receive_channel_marker( element: DataElement = output_queue.get() assert isinstance(element.payload, EmbeddedControlMessage) assert element.payload.ecm_type == EmbeddedControlMessageType.ALL_ALIGNMENT - assert element.payload.id == marker_id + assert element.payload.id == ecm_id assert element.payload.command_mapping == command_mapping assert element.payload.scope == scope assert element.tag == channel_id diff --git a/core/amber/src/main/python/core/storage/runnables/input_port_materialization_reader_runnable.py b/core/amber/src/main/python/core/storage/runnables/input_port_materialization_reader_runnable.py index 056338e7245..3aecf84f941 100644 --- a/core/amber/src/main/python/core/storage/runnables/input_port_materialization_reader_runnable.py +++ b/core/amber/src/main/python/core/storage/runnables/input_port_materialization_reader_runnable.py @@ -122,8 +122,8 @@ def tuple_to_batch_with_filter(self, tuple_: Tuple) -> typing.Iterator[DataFrame def run(self) -> None: """ Main execution logic that reads tuples from the materialized storage and - enqueues them in batches. It first emits a start marker and, when finished, - emits an end marker. Use the same partitioner implementation as that in + enqueues them in batches. It first emits a StartChannel ECM and, when finished, + emits an EndChannel ECM. Use the same partitioner implementation as that in output manager, where a tuple is batched by the partitioner and only selected as the input of this worker according to the partitioner. """ @@ -131,7 +131,7 @@ def run(self) -> None: self.materialization, self.tuple_schema = DocumentFactory.open_document( self.uri ) - self.emit_channel_marker("StartChannel", EmbeddedControlMessageType.NO_ALIGNMENT) + self.emit_ecm("StartChannel", EmbeddedControlMessageType.NO_ALIGNMENT) storage_iterator = self.materialization.get() # Iterate and process tuples. @@ -142,7 +142,7 @@ def run(self) -> None: # a batch-based iterator. for data_frame in self.tuple_to_batch_with_filter(tup): self.emit_payload(data_frame) - self.emit_channel_marker("EndChannel", EmbeddedControlMessageType.PORT_ALIGNMENT) + self.emit_ecm("EndChannel", EmbeddedControlMessageType.PORT_ALIGNMENT) except Exception as err: logger.exception(err) @@ -150,15 +150,15 @@ def stop(self): """Sets the stop flag so the run loop may terminate.""" self._stopped = True - def emit_channel_marker( + def emit_ecm( self, method_name: str, alignment: EmbeddedControlMessageType ) -> None: """ - Emit a channel marker (StartChannel or EndChannel), and + Emit an ECM (StartChannel or EndChannel), and flush the remaining data batches if any. This mimics the iterator logic of that in output manager. """ - marker_payload = EmbeddedControlMessage( + ecm = EmbeddedControlMessage( EmbeddedControlMessageIdentity(method_name), alignment, [], @@ -172,7 +172,7 @@ def emit_channel_marker( }, ) - for payload in self.partitioner.flush(self.worker_actor_id, marker_payload): + for payload in self.partitioner.flush(self.worker_actor_id, ecm): final_payload = ( payload if isinstance(payload, EmbeddedControlMessage) diff --git a/core/amber/src/main/python/proto/edu/uci/ics/amber/engine/architecture/rpc/__init__.py b/core/amber/src/main/python/proto/edu/uci/ics/amber/engine/architecture/rpc/__init__.py index 23ae9df4215..633326d91e9 100644 --- a/core/amber/src/main/python/proto/edu/uci/ics/amber/engine/architecture/rpc/__init__.py +++ b/core/amber/src/main/python/proto/edu/uci/ics/amber/engine/architecture/rpc/__init__.py @@ -176,8 +176,8 @@ class PropagateEmbeddedControlMessageRequest(betterproto.Message): ecm_type: "EmbeddedControlMessageType" = betterproto.enum_field(3) scope: List["___core__.PhysicalOpIdentity"] = betterproto.message_field(4) target_ops: List["___core__.PhysicalOpIdentity"] = betterproto.message_field(5) - marker_command: "ControlRequest" = betterproto.message_field(6) - marker_method_name: str = betterproto.string_field(7) + command: "ControlRequest" = betterproto.message_field(6) + method_name: str = betterproto.string_field(7) @dataclass(eq=False, repr=False) diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/pythonworker/PythonProxyClient.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/pythonworker/PythonProxyClient.scala index d3fb4fb6977..0fee53c01a8 100644 --- a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/pythonworker/PythonProxyClient.scala +++ b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/pythonworker/PythonProxyClient.scala @@ -135,7 +135,7 @@ class PythonProxyClient(portNumberPromise: Promise[Int], val actorId: ActorVirtu ecm: EmbeddedControlMessage, from: ChannelIdentity ): Unit = { - val descriptor = FlightDescriptor.command(PythonDataHeader(from, "ChannelMarker").toByteArray) + val descriptor = FlightDescriptor.command(PythonDataHeader(from, "ECM").toByteArray) val flightListener = new SyncPutListener val field = new Field("payload", FieldType.nullable(new ArrowType.Binary), null) diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/pythonworker/PythonProxyServer.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/pythonworker/PythonProxyServer.scala index 6b400c6166c..7faeb31b7f8 100644 --- a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/pythonworker/PythonProxyServer.scala +++ b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/pythonworker/PythonProxyServer.scala @@ -129,7 +129,7 @@ private class AmberProducer( case "State" => assert(root.getRowCount == 1) outputPort.sendTo(to, StateFrame(State(Some(ArrowUtils.getTexeraTuple(0, root))))) - case "ChannelMarker" => + case "ECM" => assert(root.getRowCount == 1) outputPort.sendTo( to, diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/DataProcessor.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/DataProcessor.scala index d0331bc5f5d..06ce7d786a6 100644 --- a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/DataProcessor.scala +++ b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/DataProcessor.scala @@ -139,7 +139,7 @@ class DataProcessor( if (outputTuple == null) return outputTuple match { case FinalizeExecutor() => - sendChannelMarkerToDataChannels(METHOD_END_CHANNEL, PORT_ALIGNMENT) + sendECMToDataChannels(METHOD_END_CHANNEL, PORT_ALIGNMENT) // Send Completed signal to worker actor. executor.close() adaptiveBatchingMonitor.stopAdaptiveBatching() @@ -245,7 +245,7 @@ class DataProcessor( } } - def sendChannelMarkerToDataChannels( + def sendECMToDataChannels( method: MethodDescriptor[EmptyRequest, EmptyReturn], alignment: EmbeddedControlMessageType ): Unit = { diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/managers/InputPortMaterializationReaderThread.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/managers/InputPortMaterializationReaderThread.scala index f6bdaf8476d..9cd14608a01 100644 --- a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/managers/InputPortMaterializationReaderThread.scala +++ b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/managers/InputPortMaterializationReaderThread.scala @@ -121,7 +121,7 @@ class InputPortMaterializationReaderThread( } /** - * Puts a ECM into the internal queue. + * Puts an ECM into the internal queue. */ private def emitECM( method: MethodDescriptor[EmptyRequest, EmptyReturn], diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/StartChannelHandler.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/StartChannelHandler.scala index 1bbd9eec257..36918015388 100644 --- a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/StartChannelHandler.scala +++ b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/StartChannelHandler.scala @@ -35,7 +35,7 @@ trait StartChannelHandler { ctx: AsyncRPCContext ): Future[EmptyReturn] = { val portId = dp.inputGateway.getChannel(dp.inputManager.currentChannelId).getPortId - dp.sendChannelMarkerToDataChannels(METHOD_START_CHANNEL, NO_ALIGNMENT) + dp.sendECMToDataChannels(METHOD_START_CHANNEL, NO_ALIGNMENT) try { val outputState = dp.executor.produceStateOnStart(portId.id) if (outputState.isDefined) { From 81ca4da8be5a674685e95bc636bad6610321d337 Mon Sep 17 00:00:00 2001 From: Xinyuan Lin Date: Sat, 14 Jun 2025 05:30:07 -0700 Subject: [PATCH 04/39] update --- .../managers/state_processing_manager.py | 2 +- .../sendsemantics/broad_cast_partitioner.py | 4 +-- .../hash_based_shuffle_partitioner.py | 4 +-- .../sendsemantics/one_to_one_partitioner.py | 4 +-- .../architecture/sendsemantics/partitioner.py | 2 +- .../range_based_shuffle_partitioner.py | 4 +-- .../sendsemantics/round_robin_partitioner.py | 4 +-- .../python/core/runnables/data_processor.py | 2 +- .../EmbeddedControlMessageHandler.scala | 6 ++-- .../RetrieveWorkflowStateHandler.scala | 4 +-- .../architecture/worker/DataProcessor.scala | 31 +++++++++---------- .../EmbeddedControlMessageManager.scala | 4 +-- .../architecture/worker/PauseType.scala | 2 +- 13 files changed, 36 insertions(+), 37 deletions(-) diff --git a/core/amber/src/main/python/core/architecture/managers/state_processing_manager.py b/core/amber/src/main/python/core/architecture/managers/state_processing_manager.py index da3a3324a73..56f91c6b137 100644 --- a/core/amber/src/main/python/core/architecture/managers/state_processing_manager.py +++ b/core/amber/src/main/python/core/architecture/managers/state_processing_manager.py @@ -24,7 +24,7 @@ def __init__(self): self.current_input_state: Optional[State] = None self.current_output_state: Optional[State] = None - def get_input_marker(self) -> Optional[State]: + def get_input_state(self) -> Optional[State]: ret, self.current_input_state = self.current_input_state, None return ret diff --git a/core/amber/src/main/python/core/architecture/sendsemantics/broad_cast_partitioner.py b/core/amber/src/main/python/core/architecture/sendsemantics/broad_cast_partitioner.py index eeb1ca43ef3..e8d5eab6250 100644 --- a/core/amber/src/main/python/core/architecture/sendsemantics/broad_cast_partitioner.py +++ b/core/amber/src/main/python/core/architecture/sendsemantics/broad_cast_partitioner.py @@ -51,7 +51,7 @@ def add_tuple_to_batch( @overrides def flush( - self, to: ActorVirtualIdentity, marker: EmbeddedControlMessage + self, to: ActorVirtualIdentity, ecm: EmbeddedControlMessage ) -> Iterator[typing.Union[EmbeddedControlMessage, typing.List[Tuple]]]: if len(self.batch) > 0: for receiver in self.receivers: @@ -60,7 +60,7 @@ def flush( self.reset() for receiver in self.receivers: if receiver == to: - yield marker + yield ecm @overrides def flush_state( diff --git a/core/amber/src/main/python/core/architecture/sendsemantics/hash_based_shuffle_partitioner.py b/core/amber/src/main/python/core/architecture/sendsemantics/hash_based_shuffle_partitioner.py index 7ea045da52e..c5bc8dec2a8 100644 --- a/core/amber/src/main/python/core/architecture/sendsemantics/hash_based_shuffle_partitioner.py +++ b/core/amber/src/main/python/core/architecture/sendsemantics/hash_based_shuffle_partitioner.py @@ -61,13 +61,13 @@ def add_tuple_to_batch( @overrides def flush( - self, to: ActorVirtualIdentity, marker: EmbeddedControlMessage + self, to: ActorVirtualIdentity, ecm: EmbeddedControlMessage ) -> Iterator[typing.Union[EmbeddedControlMessage, typing.List[Tuple]]]: for receiver, batch in self.receivers: if receiver == to: if len(batch) > 0: yield batch - yield marker + yield ecm @overrides def flush_state( diff --git a/core/amber/src/main/python/core/architecture/sendsemantics/one_to_one_partitioner.py b/core/amber/src/main/python/core/architecture/sendsemantics/one_to_one_partitioner.py index a259d7c0aff..729e6dfe001 100644 --- a/core/amber/src/main/python/core/architecture/sendsemantics/one_to_one_partitioner.py +++ b/core/amber/src/main/python/core/architecture/sendsemantics/one_to_one_partitioner.py @@ -52,12 +52,12 @@ def add_tuple_to_batch( @overrides def flush( - self, to: ActorVirtualIdentity, marker: EmbeddedControlMessage + self, to: ActorVirtualIdentity, ecm: EmbeddedControlMessage ) -> Iterator[typing.Union[EmbeddedControlMessage, typing.List[Tuple]]]: if len(self.batch) > 0: yield self.batch self.reset() - yield marker + yield ecm @overrides def flush_state( diff --git a/core/amber/src/main/python/core/architecture/sendsemantics/partitioner.py b/core/amber/src/main/python/core/architecture/sendsemantics/partitioner.py index 343c06080a2..421c72433d5 100644 --- a/core/amber/src/main/python/core/architecture/sendsemantics/partitioner.py +++ b/core/amber/src/main/python/core/architecture/sendsemantics/partitioner.py @@ -37,7 +37,7 @@ def add_tuple_to_batch( pass def flush( - self, to: ActorVirtualIdentity, marker: EmbeddedControlMessage + self, to: ActorVirtualIdentity, ecm: EmbeddedControlMessage ) -> Iterator[typing.Union[EmbeddedControlMessage, typing.List[Tuple]]]: pass diff --git a/core/amber/src/main/python/core/architecture/sendsemantics/range_based_shuffle_partitioner.py b/core/amber/src/main/python/core/architecture/sendsemantics/range_based_shuffle_partitioner.py index 4cf2f0b8922..9b4b3cada69 100644 --- a/core/amber/src/main/python/core/architecture/sendsemantics/range_based_shuffle_partitioner.py +++ b/core/amber/src/main/python/core/architecture/sendsemantics/range_based_shuffle_partitioner.py @@ -75,13 +75,13 @@ def add_tuple_to_batch( @overrides def flush( - self, to: ActorVirtualIdentity, marker: EmbeddedControlMessage + self, to: ActorVirtualIdentity, ecm: EmbeddedControlMessage ) -> Iterator[typing.Union[EmbeddedControlMessage, typing.List[Tuple]]]: for receiver, batch in self.receivers: if receiver == to: if len(batch) > 0: yield batch - yield marker + yield ecm @overrides def flush_state( diff --git a/core/amber/src/main/python/core/architecture/sendsemantics/round_robin_partitioner.py b/core/amber/src/main/python/core/architecture/sendsemantics/round_robin_partitioner.py index 725095539f4..ab5b0a69e4d 100644 --- a/core/amber/src/main/python/core/architecture/sendsemantics/round_robin_partitioner.py +++ b/core/amber/src/main/python/core/architecture/sendsemantics/round_robin_partitioner.py @@ -62,14 +62,14 @@ def add_tuple_to_batch( @overrides def flush( - self, to: ActorVirtualIdentity, marker: EmbeddedControlMessage + self, to: ActorVirtualIdentity, ecm: EmbeddedControlMessage ) -> Iterator[typing.Union[EmbeddedControlMessage, typing.List[Tuple]]]: for receiver, batch in self.receivers: if receiver == to: if len(batch) > 0: yield batch batch.clear() - yield marker + yield ecm @overrides def flush_state( diff --git a/core/amber/src/main/python/core/runnables/data_processor.py b/core/amber/src/main/python/core/runnables/data_processor.py index 8f6b078e274..7130c7676b8 100644 --- a/core/amber/src/main/python/core/runnables/data_processor.py +++ b/core/amber/src/main/python/core/runnables/data_processor.py @@ -52,7 +52,7 @@ def run(self) -> None: self._switch_context() while self._running.is_set(): marker = self._context.tuple_processing_manager.get_internal_marker() - state = self._context.state_processing_manager.get_input_marker() + state = self._context.state_processing_manager.get_input_state() tuple_ = self._context.tuple_processing_manager.current_input_tuple if marker is not None: self.process_internal_marker(marker) diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/controller/promisehandlers/EmbeddedControlMessageHandler.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/controller/promisehandlers/EmbeddedControlMessageHandler.scala index 3ecffacc6f6..a91bd14c5b8 100644 --- a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/controller/promisehandlers/EmbeddedControlMessageHandler.scala +++ b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/controller/promisehandlers/EmbeddedControlMessageHandler.scala @@ -46,7 +46,7 @@ trait EmbeddedControlMessageHandler { cp.workflowExecution.getRunningRegionExecutions .map(_.getOperatorExecution(target)) .flatMap(_.getWorkerIds.map { worker => - worker -> createInvocation(msg.markerMethodName, msg.markerCommand, worker) + worker -> createInvocation(msg.methodName, msg.command, worker) }) } // step 2: packing all control commands into one compound command. @@ -81,7 +81,7 @@ trait EmbeddedControlMessageHandler { val finalScope = channelScope ++ controlChannels - // step 4: start prop, send marker through control channel with the compound command from sources. + // step 4: start prop, send ECM through control channel with the compound command from sources. msg.sourceOpToStartProp.foreach { source => cp.workflowExecution.getLatestOperatorExecution(source).getWorkerIds.foreach { worker => sendECM( @@ -94,7 +94,7 @@ trait EmbeddedControlMessageHandler { } } - // step 5: wait for the marker propagation. + // step 5: wait for the ECM propagation. Future.collect(futures.toList).map { ret => cp.logManager.markAsReplayDestination(msg.id) PropagateEmbeddedControlMessageResponse(ret.map(x => (x._1.name, x._2)).toMap) diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/controller/promisehandlers/RetrieveWorkflowStateHandler.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/controller/promisehandlers/RetrieveWorkflowStateHandler.scala index d70ef541122..b783b1c749a 100644 --- a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/controller/promisehandlers/RetrieveWorkflowStateHandler.scala +++ b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/controller/promisehandlers/RetrieveWorkflowStateHandler.scala @@ -45,7 +45,7 @@ trait RetrieveWorkflowStateHandler { ctx: AsyncRPCContext ): Future[RetrieveWorkflowStateResponse] = { val targetOps = cp.workflowScheduler.physicalPlan.operators.map(_.id).toSeq - val markerMessage = PropagateEmbeddedControlMessageRequest( + val msg = PropagateEmbeddedControlMessageRequest( cp.workflowExecution.getRunningRegionExecutions .flatMap(_.getAllOperatorExecutions.map(_._1)) .toSeq, @@ -58,7 +58,7 @@ trait RetrieveWorkflowStateHandler { ) controllerInterface .propagateEmbeddedControlMessage( - markerMessage, + msg, mkContext(SELF) ) .map { ret => diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/DataProcessor.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/DataProcessor.scala index 06ce7d786a6..f8bec7cc905 100644 --- a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/DataProcessor.scala +++ b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/DataProcessor.scala @@ -208,39 +208,38 @@ class DataProcessor( def processECM( channelId: ChannelIdentity, - marker: EmbeddedControlMessage, + ecm: EmbeddedControlMessage, logManager: ReplayLogManager ): Unit = { inputManager.currentChannelId = channelId - val markerId = marker.id - val command = marker.commandMapping.get(actorId.name) - logger.info(s"receive marker from $channelId, id = $markerId, cmd = $command") - if (marker.ecmType != NO_ALIGNMENT) { - pauseManager.pauseInputChannel(EpochMarkerPause(markerId), List(channelId)) + val command = ecm.commandMapping.get(actorId.name) + logger.info(s"receive ECM from $channelId, id = ${ecm.id}, cmd = $command") + if (ecm.ecmType != NO_ALIGNMENT) { + pauseManager.pauseInputChannel(ECMPause(ecm.id), List(channelId)) } - if (ecmManager.isMarkerAligned(channelId, marker)) { - logManager.markAsReplayDestination(markerId) - // invoke the control command carried with the epoch marker - logger.info(s"process marker from $channelId, id = $markerId, cmd = $command") + if (ecmManager.ecmAligned(channelId, ecm)) { + logManager.markAsReplayDestination(ecm.id) + // invoke the control command carried with the ECM + logger.info(s"process ECM from $channelId, id = ${ecm.id}, cmd = $command") if (command.isDefined) { asyncRPCServer.receive(command.get, channelId.fromWorkerId) } - // if this worker is not the final destination of the marker, pass it downstream - val downstreamChannelsInScope = marker.scope.filter(_.fromWorkerId == actorId).toSet + // if this worker is not the final destination of the ECM, pass it downstream + val downstreamChannelsInScope = ecm.scope.filter(_.fromWorkerId == actorId).toSet if (downstreamChannelsInScope.nonEmpty) { outputManager.flush(Some(downstreamChannelsInScope)) outputGateway.getActiveChannels.foreach { activeChannelId => if (downstreamChannelsInScope.contains(activeChannelId)) { logger.info( - s"send marker to $activeChannelId, id = $markerId, cmd = $command" + s"send ECM to $activeChannelId, id = ${ecm.id}, cmd = $command" ) - outputGateway.sendTo(activeChannelId, marker) + outputGateway.sendTo(activeChannelId, ecm) } } } // unblock input channels - if (marker.ecmType != NO_ALIGNMENT) { - pauseManager.resume(EpochMarkerPause(markerId)) + if (ecm.ecmType != NO_ALIGNMENT) { + pauseManager.resume(ECMPause(ecm.id)) } } } diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/EmbeddedControlMessageManager.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/EmbeddedControlMessageManager.scala index 1fcb22a2ba4..38e934f8cce 100644 --- a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/EmbeddedControlMessageManager.scala +++ b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/EmbeddedControlMessageManager.scala @@ -46,7 +46,7 @@ class EmbeddedControlMessageManager( val checkpoints = new mutable.HashMap[EmbeddedControlMessageIdentity, CheckpointState]() /** - * Determines if an epoch ECM is fully received from all relevant senders within its scope. + * Determines if an ECM is fully received from all relevant senders within its scope. * This method checks if the ECM, based on its type, has been received from all necessary channels. * For ECMs requiring alignment, it verifies receipt from all senders in the scope. For non-aligned ECMs, * it checks if it's the first received ECM. Post verification, it cleans up the ECMs. @@ -54,7 +54,7 @@ class EmbeddedControlMessageManager( * @return Boolean indicating if the ECM is completely received from all senders * within the scope. Returns true if the ECM is aligned, otherwise false. */ - def isMarkerAligned( + def ecmAligned( from: ChannelIdentity, ecm: EmbeddedControlMessage ): Boolean = { diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/PauseType.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/PauseType.scala index 770a6bcb1b0..70cac2e9dcc 100644 --- a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/PauseType.scala +++ b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/PauseType.scala @@ -31,4 +31,4 @@ object OperatorLogicPause extends PauseType object SchedulerTimeSlotExpiredPause extends PauseType -case class EpochMarkerPause(id: EmbeddedControlMessageIdentity) extends PauseType +case class ECMPause(id: EmbeddedControlMessageIdentity) extends PauseType From 8d5c4443cc1325b0038e29dcc931f4a9fc824d7d Mon Sep 17 00:00:00 2001 From: Xinyuan Lin Date: Sat, 14 Jun 2025 05:38:28 -0700 Subject: [PATCH 05/39] fix fmt --- .../embedded_control_message_manager.py | 6 ++-- .../main/python/core/models/internal_queue.py | 4 ++- .../main/python/core/runnables/main_loop.py | 7 ++-- .../python/core/runnables/network_sender.py | 4 +-- .../python/core/runnables/test_main_loop.py | 10 ++++-- ...ut_port_materialization_reader_runnable.py | 4 +-- .../architecture/controller/Controller.scala | 4 +-- .../architecture/worker/DataProcessor.scala | 36 +++++++++++++++---- .../EmbeddedControlMessageManager.scala | 3 +- .../architecture/worker/WorkflowWorker.scala | 5 ++- 10 files changed, 56 insertions(+), 27 deletions(-) diff --git a/core/amber/src/main/python/core/architecture/managers/embedded_control_message_manager.py b/core/amber/src/main/python/core/architecture/managers/embedded_control_message_manager.py index 72752d8a169..e10ad2c939d 100644 --- a/core/amber/src/main/python/core/architecture/managers/embedded_control_message_manager.py +++ b/core/amber/src/main/python/core/architecture/managers/embedded_control_message_manager.py @@ -49,9 +49,9 @@ def ecm_aligned( """ self.ecm_received[ecm.id].add(from_channel) - ecm_received_from_all_channels = self.get_channels_within_scope( - ecm - ).issubset(self.ecm_received[ecm.id]) + ecm_received_from_all_channels = self.get_channels_within_scope(ecm).issubset( + self.ecm_received[ecm.id] + ) if ecm.ecm_type == EmbeddedControlMessageType.ALL_ALIGNMENT: ecm_completed = ecm_received_from_all_channels diff --git a/core/amber/src/main/python/core/models/internal_queue.py b/core/amber/src/main/python/core/models/internal_queue.py index 8debc362522..303506d5e97 100644 --- a/core/amber/src/main/python/core/models/internal_queue.py +++ b/core/amber/src/main/python/core/models/internal_queue.py @@ -80,7 +80,9 @@ def put(self, item: T) -> None: if item.tag not in self._queue_ids: self._queue.add_sub_queue(item.tag, 1 if item.tag.is_control else 2) self._queue_ids.add(item.tag) - if isinstance(item, (DataElement, InternalMarker, EmbeddedControlMessageElement)): + if isinstance( + item, (DataElement, InternalMarker, EmbeddedControlMessageElement) + ): self._queue.put(item.tag, item) elif isinstance(item, ControlElement): self._queue.put(item.tag, item) diff --git a/core/amber/src/main/python/core/runnables/main_loop.py b/core/amber/src/main/python/core/runnables/main_loop.py index 140ff5d404f..ec78b7b16ea 100644 --- a/core/amber/src/main/python/core/runnables/main_loop.py +++ b/core/amber/src/main/python/core/runnables/main_loop.py @@ -308,8 +308,7 @@ def _process_ecm(self, ecm_element: EmbeddedControlMessageElement): command = ecm.command_mapping.get(self.context.worker_id) channel_id = self.context.current_input_channel_id logger.info( - f"receive channel ECM from {channel_id}," - f" id = {ecm.id}, cmd = {command}" + f"receive channel ECM from {channel_id}," f" id = {ecm.id}, cmd = {command}" ) if ecm.ecm_type != EmbeddedControlMessageType.NO_ALIGNMENT: self.context.pause_manager.pause_input_channel( @@ -375,9 +374,7 @@ def _send_ecm_to_data_channels( def _send_ecm( self, channel_id: ChannelIdentity, ecm: EmbeddedControlMessage ) -> None: - for batch in self.context.output_manager.emit_ecm( - channel_id.to_worker_id, ecm - ): + for batch in self.context.output_manager.emit_ecm(channel_id.to_worker_id, ecm): tag = channel_id element = ( EmbeddedControlMessageElement(tag=tag, payload=batch) diff --git a/core/amber/src/main/python/core/runnables/network_sender.py b/core/amber/src/main/python/core/runnables/network_sender.py index b29dae3e85b..bd682be7faa 100644 --- a/core/amber/src/main/python/core/runnables/network_sender.py +++ b/core/amber/src/main/python/core/runnables/network_sender.py @@ -68,9 +68,7 @@ def receive(self, next_entry: InternalQueueElement): raise TypeError(f"Unexpected entry {next_entry}") @logger.catch(reraise=True) - def _send_ecm( - self, to: ChannelIdentity, ecm: EmbeddedControlMessage - ) -> None: + def _send_ecm(self, to: ChannelIdentity, ecm: EmbeddedControlMessage) -> None: """ Sends an ECM to the specified channel. diff --git a/core/amber/src/main/python/core/runnables/test_main_loop.py b/core/amber/src/main/python/core/runnables/test_main_loop.py index 2d65e6d195b..699463373e8 100644 --- a/core/amber/src/main/python/core/runnables/test_main_loop.py +++ b/core/amber/src/main/python/core/runnables/test_main_loop.py @@ -28,7 +28,11 @@ InternalQueue, Tuple, ) -from core.models.internal_queue import DataElement, ControlElement, EmbeddedControlMessageElement +from core.models.internal_queue import ( + DataElement, + ControlElement, + EmbeddedControlMessageElement, +) from core.runnables import MainLoop from core.util import set_one_of from proto.edu.uci.ics.amber.core import ( @@ -1169,7 +1173,9 @@ def test_main_loop_thread_can_align_ecm( "test_ecm", EmbeddedControlMessageType.ALL_ALIGNMENT, scope, command_mapping ) input_queue.put( - EmbeddedControlMessageElement(tag=mock_control_input_channel, payload=test_ecm) + EmbeddedControlMessageElement( + tag=mock_control_input_channel, payload=test_ecm + ) ) input_queue.put(mock_binary_data_element) input_queue.put( diff --git a/core/amber/src/main/python/core/storage/runnables/input_port_materialization_reader_runnable.py b/core/amber/src/main/python/core/storage/runnables/input_port_materialization_reader_runnable.py index 3aecf84f941..64793f4955c 100644 --- a/core/amber/src/main/python/core/storage/runnables/input_port_materialization_reader_runnable.py +++ b/core/amber/src/main/python/core/storage/runnables/input_port_materialization_reader_runnable.py @@ -150,9 +150,7 @@ def stop(self): """Sets the stop flag so the run loop may terminate.""" self._stopped = True - def emit_ecm( - self, method_name: str, alignment: EmbeddedControlMessageType - ) -> None: + def emit_ecm(self, method_name: str, alignment: EmbeddedControlMessageType) -> None: """ Emit an ECM (StartChannel or EndChannel), and flush the remaining data batches if any. This mimics the diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/controller/Controller.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/controller/Controller.scala index 83616a688c2..27278c1e7f5 100644 --- a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/controller/Controller.scala +++ b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/controller/Controller.scala @@ -138,9 +138,9 @@ class Controller( val msgToLog = Some(msg).filter(_.payload.isInstanceOf[ControlPayload]) logManager.withFaultTolerant(msg.channelId, msgToLog) { msg.payload match { - case payload: ControlPayload => cp.processControlPayload(msg.channelId, payload) + case payload: ControlPayload => cp.processControlPayload(msg.channelId, payload) case _: EmbeddedControlMessage => // skip ECM - case p => throw new RuntimeException(s"controller cannot handle $p") + case p => throw new RuntimeException(s"controller cannot handle $p") } } case None => diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/DataProcessor.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/DataProcessor.scala index f8bec7cc905..346da8dd552 100644 --- a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/DataProcessor.scala +++ b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/DataProcessor.scala @@ -22,21 +22,45 @@ package edu.uci.ics.amber.engine.architecture.worker import com.softwaremill.macwire.wire import edu.uci.ics.amber.core.executor.OperatorExecutor import edu.uci.ics.amber.core.state.State -import edu.uci.ics.amber.core.tuple.{FinalizeExecutor, FinalizePort, SchemaEnforceable, Tuple, TupleLike} +import edu.uci.ics.amber.core.tuple.{ + FinalizeExecutor, + FinalizePort, + SchemaEnforceable, + Tuple, + TupleLike +} import edu.uci.ics.amber.engine.architecture.common.AmberProcessor import edu.uci.ics.amber.engine.architecture.logreplay.ReplayLogManager -import edu.uci.ics.amber.engine.architecture.messaginglayer.{InputManager, OutputManager, WorkerTimerService} -import edu.uci.ics.amber.engine.architecture.rpc.controlcommands.EmbeddedControlMessageType.{NO_ALIGNMENT, PORT_ALIGNMENT} +import edu.uci.ics.amber.engine.architecture.messaginglayer.{ + InputManager, + OutputManager, + WorkerTimerService +} +import edu.uci.ics.amber.engine.architecture.rpc.controlcommands.EmbeddedControlMessageType.{ + NO_ALIGNMENT, + PORT_ALIGNMENT +} import edu.uci.ics.amber.engine.architecture.rpc.controlcommands._ -import edu.uci.ics.amber.engine.architecture.worker.WorkflowWorker.{DPInputQueueElement, MainThreadDelegateMessage} +import edu.uci.ics.amber.engine.architecture.worker.WorkflowWorker.{ + DPInputQueueElement, + MainThreadDelegateMessage +} import edu.uci.ics.amber.engine.architecture.worker.managers.SerializationManager -import edu.uci.ics.amber.engine.architecture.worker.statistics.WorkerState.{COMPLETED, READY, RUNNING} +import edu.uci.ics.amber.engine.architecture.worker.statistics.WorkerState.{ + COMPLETED, + READY, + RUNNING +} import edu.uci.ics.amber.engine.architecture.worker.statistics.WorkerStatistics import edu.uci.ics.amber.engine.common.ambermessage._ import edu.uci.ics.amber.engine.common.statetransition.WorkerStateManager import edu.uci.ics.amber.engine.common.virtualidentity.util.CONTROLLER import edu.uci.ics.amber.error.ErrorUtils.{mkConsoleMessage, safely} -import edu.uci.ics.amber.core.virtualidentity.{ActorVirtualIdentity, ChannelIdentity, EmbeddedControlMessageIdentity} +import edu.uci.ics.amber.core.virtualidentity.{ + ActorVirtualIdentity, + ChannelIdentity, + EmbeddedControlMessageIdentity +} import edu.uci.ics.amber.core.workflow.PortIdentity import edu.uci.ics.amber.engine.architecture.rpc.controlreturns.EmptyReturn import edu.uci.ics.amber.engine.architecture.rpc.workerservice.WorkerServiceGrpc.METHOD_END_CHANNEL diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/EmbeddedControlMessageManager.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/EmbeddedControlMessageManager.scala index 38e934f8cce..cfdce52d76d 100644 --- a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/EmbeddedControlMessageManager.scala +++ b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/EmbeddedControlMessageManager.scala @@ -41,7 +41,8 @@ class EmbeddedControlMessageManager( inputManager: InputManager ) extends AmberLogging { - private val ecmReceived = new mutable.HashMap[EmbeddedControlMessageIdentity, Set[ChannelIdentity]]() + private val ecmReceived = + new mutable.HashMap[EmbeddedControlMessageIdentity, Set[ChannelIdentity]]() val checkpoints = new mutable.HashMap[EmbeddedControlMessageIdentity, CheckpointState]() diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/WorkflowWorker.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/WorkflowWorker.scala index 5a99f693c4c..0f24e51f8d3 100644 --- a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/WorkflowWorker.scala +++ b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/WorkflowWorker.scala @@ -66,7 +66,10 @@ object WorkflowWorker { faultToleranceConfOpt: Option[FaultToleranceConfig] = None ) - final case class StateRestoreConfig(readFrom: URI, replayDestination: EmbeddedControlMessageIdentity) + final case class StateRestoreConfig( + readFrom: URI, + replayDestination: EmbeddedControlMessageIdentity + ) final case class FaultToleranceConfig(writeTo: URI) } From 5522304c6ced9b84414ce611d4ce0708fb56bb4f Mon Sep 17 00:00:00 2001 From: Xinyuan Lin Date: Sat, 14 Jun 2025 16:02:40 -0700 Subject: [PATCH 06/39] fix fmt --- core/amber/src/main/python/proto/__init__.py | 16 ++++++++++++++++ core/amber/src/main/python/proto/edu/__init__.py | 16 ++++++++++++++++ .../src/main/python/proto/edu/uci/__init__.py | 16 ++++++++++++++++ 3 files changed, 48 insertions(+) diff --git a/core/amber/src/main/python/proto/__init__.py b/core/amber/src/main/python/proto/__init__.py index e69de29bb2d..d216be4ddc9 100644 --- a/core/amber/src/main/python/proto/__init__.py +++ b/core/amber/src/main/python/proto/__init__.py @@ -0,0 +1,16 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. \ No newline at end of file diff --git a/core/amber/src/main/python/proto/edu/__init__.py b/core/amber/src/main/python/proto/edu/__init__.py index e69de29bb2d..d216be4ddc9 100644 --- a/core/amber/src/main/python/proto/edu/__init__.py +++ b/core/amber/src/main/python/proto/edu/__init__.py @@ -0,0 +1,16 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. \ No newline at end of file diff --git a/core/amber/src/main/python/proto/edu/uci/__init__.py b/core/amber/src/main/python/proto/edu/uci/__init__.py index e69de29bb2d..d216be4ddc9 100644 --- a/core/amber/src/main/python/proto/edu/uci/__init__.py +++ b/core/amber/src/main/python/proto/edu/uci/__init__.py @@ -0,0 +1,16 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. \ No newline at end of file From 0dc7bc1076b7d9e1a14b598a896870088668e7df Mon Sep 17 00:00:00 2001 From: Xinyuan Lin Date: Sat, 14 Jun 2025 16:04:59 -0700 Subject: [PATCH 07/39] fix fmt --- .../main/python/proto/edu/uci/ics/__init__.py | 16 ++++++++++++++++ .../python/proto/edu/uci/ics/amber/__init__.py | 16 ++++++++++++++++ .../proto/edu/uci/ics/amber/engine/__init__.py | 16 ++++++++++++++++ .../ics/amber/engine/architecture/__init__.py | 16 ++++++++++++++++ .../amber/engine/architecture/rpc/__init__.py | 17 +++++++++++++++++ .../edu/uci/ics/amber/engine/common/__init__.py | 17 +++++++++++++++++ 6 files changed, 98 insertions(+) diff --git a/core/amber/src/main/python/proto/edu/uci/ics/__init__.py b/core/amber/src/main/python/proto/edu/uci/ics/__init__.py index e69de29bb2d..d216be4ddc9 100644 --- a/core/amber/src/main/python/proto/edu/uci/ics/__init__.py +++ b/core/amber/src/main/python/proto/edu/uci/ics/__init__.py @@ -0,0 +1,16 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. \ No newline at end of file diff --git a/core/amber/src/main/python/proto/edu/uci/ics/amber/__init__.py b/core/amber/src/main/python/proto/edu/uci/ics/amber/__init__.py index e69de29bb2d..d216be4ddc9 100644 --- a/core/amber/src/main/python/proto/edu/uci/ics/amber/__init__.py +++ b/core/amber/src/main/python/proto/edu/uci/ics/amber/__init__.py @@ -0,0 +1,16 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. \ No newline at end of file diff --git a/core/amber/src/main/python/proto/edu/uci/ics/amber/engine/__init__.py b/core/amber/src/main/python/proto/edu/uci/ics/amber/engine/__init__.py index e69de29bb2d..d216be4ddc9 100644 --- a/core/amber/src/main/python/proto/edu/uci/ics/amber/engine/__init__.py +++ b/core/amber/src/main/python/proto/edu/uci/ics/amber/engine/__init__.py @@ -0,0 +1,16 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. \ No newline at end of file diff --git a/core/amber/src/main/python/proto/edu/uci/ics/amber/engine/architecture/__init__.py b/core/amber/src/main/python/proto/edu/uci/ics/amber/engine/architecture/__init__.py index e69de29bb2d..d216be4ddc9 100644 --- a/core/amber/src/main/python/proto/edu/uci/ics/amber/engine/architecture/__init__.py +++ b/core/amber/src/main/python/proto/edu/uci/ics/amber/engine/architecture/__init__.py @@ -0,0 +1,16 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. \ No newline at end of file diff --git a/core/amber/src/main/python/proto/edu/uci/ics/amber/engine/architecture/rpc/__init__.py b/core/amber/src/main/python/proto/edu/uci/ics/amber/engine/architecture/rpc/__init__.py index 633326d91e9..ae4a12611b1 100644 --- a/core/amber/src/main/python/proto/edu/uci/ics/amber/engine/architecture/rpc/__init__.py +++ b/core/amber/src/main/python/proto/edu/uci/ics/amber/engine/architecture/rpc/__init__.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + # Generated by the protocol buffer compiler. DO NOT EDIT! # sources: edu/uci/ics/amber/engine/architecture/rpc/controlcommands.proto, edu/uci/ics/amber/engine/architecture/rpc/controllerservice.proto, edu/uci/ics/amber/engine/architecture/rpc/controlreturns.proto, edu/uci/ics/amber/engine/architecture/rpc/testerservice.proto, edu/uci/ics/amber/engine/architecture/rpc/workerservice.proto # plugin: python-betterproto diff --git a/core/amber/src/main/python/proto/edu/uci/ics/amber/engine/common/__init__.py b/core/amber/src/main/python/proto/edu/uci/ics/amber/engine/common/__init__.py index 48771d4c814..061f0370aa4 100644 --- a/core/amber/src/main/python/proto/edu/uci/ics/amber/engine/common/__init__.py +++ b/core/amber/src/main/python/proto/edu/uci/ics/amber/engine/common/__init__.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + # Generated by the protocol buffer compiler. DO NOT EDIT! # sources: edu/uci/ics/amber/engine/common/actormessage.proto, edu/uci/ics/amber/engine/common/ambermessage.proto, edu/uci/ics/amber/engine/common/executionruntimestate.proto # plugin: python-betterproto From 1c0cda63e0e47cd23a1980b448c619c31c72d12d Mon Sep 17 00:00:00 2001 From: Xinyuan Lin Date: Sat, 14 Jun 2025 16:06:26 -0700 Subject: [PATCH 08/39] fix fmt --- .../proto/edu/uci/ics/amber/core/__init__.py | 17 +++++++++++++++++ .../architecture/sendsemantics/__init__.py | 17 +++++++++++++++++ .../engine/architecture/worker/__init__.py | 17 +++++++++++++++++ .../src/main/python/proto/scalapb/__init__.py | 17 +++++++++++++++++ 4 files changed, 68 insertions(+) diff --git a/core/amber/src/main/python/proto/edu/uci/ics/amber/core/__init__.py b/core/amber/src/main/python/proto/edu/uci/ics/amber/core/__init__.py index cc12d01852e..1e57298fe5f 100644 --- a/core/amber/src/main/python/proto/edu/uci/ics/amber/core/__init__.py +++ b/core/amber/src/main/python/proto/edu/uci/ics/amber/core/__init__.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + # Generated by the protocol buffer compiler. DO NOT EDIT! # sources: edu/uci/ics/amber/core/executor.proto, edu/uci/ics/amber/core/virtualidentity.proto, edu/uci/ics/amber/core/workflow.proto, edu/uci/ics/amber/core/workflowruntimestate.proto # plugin: python-betterproto diff --git a/core/amber/src/main/python/proto/edu/uci/ics/amber/engine/architecture/sendsemantics/__init__.py b/core/amber/src/main/python/proto/edu/uci/ics/amber/engine/architecture/sendsemantics/__init__.py index b9769dc2bb9..0b10145b8a2 100644 --- a/core/amber/src/main/python/proto/edu/uci/ics/amber/engine/architecture/sendsemantics/__init__.py +++ b/core/amber/src/main/python/proto/edu/uci/ics/amber/engine/architecture/sendsemantics/__init__.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + # Generated by the protocol buffer compiler. DO NOT EDIT! # sources: edu/uci/ics/amber/engine/architecture/sendsemantics/partitionings.proto # plugin: python-betterproto diff --git a/core/amber/src/main/python/proto/edu/uci/ics/amber/engine/architecture/worker/__init__.py b/core/amber/src/main/python/proto/edu/uci/ics/amber/engine/architecture/worker/__init__.py index 8b87e847f58..f5fd615493d 100644 --- a/core/amber/src/main/python/proto/edu/uci/ics/amber/engine/architecture/worker/__init__.py +++ b/core/amber/src/main/python/proto/edu/uci/ics/amber/engine/architecture/worker/__init__.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + # Generated by the protocol buffer compiler. DO NOT EDIT! # sources: edu/uci/ics/amber/engine/architecture/worker/statistics.proto # plugin: python-betterproto diff --git a/core/amber/src/main/python/proto/scalapb/__init__.py b/core/amber/src/main/python/proto/scalapb/__init__.py index 49c713815a5..153dd1b07aa 100644 --- a/core/amber/src/main/python/proto/scalapb/__init__.py +++ b/core/amber/src/main/python/proto/scalapb/__init__.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + # Generated by the protocol buffer compiler. DO NOT EDIT! # sources: scalapb/scalapb.proto # plugin: python-betterproto From 8fdec07038164346624c74d196a47b303ba0e317 Mon Sep 17 00:00:00 2001 From: Xinyuan Lin Date: Sat, 14 Jun 2025 16:22:46 -0700 Subject: [PATCH 09/39] fix fmt --- core/amber/src/main/python/proto/__init__.py | 2 +- core/amber/src/main/python/proto/edu/__init__.py | 2 +- core/amber/src/main/python/proto/edu/uci/__init__.py | 2 +- core/amber/src/main/python/proto/edu/uci/ics/__init__.py | 2 +- core/amber/src/main/python/proto/edu/uci/ics/amber/__init__.py | 2 +- .../src/main/python/proto/edu/uci/ics/amber/engine/__init__.py | 2 +- .../proto/edu/uci/ics/amber/engine/architecture/__init__.py | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/core/amber/src/main/python/proto/__init__.py b/core/amber/src/main/python/proto/__init__.py index d216be4ddc9..13a83393a91 100644 --- a/core/amber/src/main/python/proto/__init__.py +++ b/core/amber/src/main/python/proto/__init__.py @@ -13,4 +13,4 @@ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations -# under the License. \ No newline at end of file +# under the License. diff --git a/core/amber/src/main/python/proto/edu/__init__.py b/core/amber/src/main/python/proto/edu/__init__.py index d216be4ddc9..13a83393a91 100644 --- a/core/amber/src/main/python/proto/edu/__init__.py +++ b/core/amber/src/main/python/proto/edu/__init__.py @@ -13,4 +13,4 @@ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations -# under the License. \ No newline at end of file +# under the License. diff --git a/core/amber/src/main/python/proto/edu/uci/__init__.py b/core/amber/src/main/python/proto/edu/uci/__init__.py index d216be4ddc9..13a83393a91 100644 --- a/core/amber/src/main/python/proto/edu/uci/__init__.py +++ b/core/amber/src/main/python/proto/edu/uci/__init__.py @@ -13,4 +13,4 @@ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations -# under the License. \ No newline at end of file +# under the License. diff --git a/core/amber/src/main/python/proto/edu/uci/ics/__init__.py b/core/amber/src/main/python/proto/edu/uci/ics/__init__.py index d216be4ddc9..13a83393a91 100644 --- a/core/amber/src/main/python/proto/edu/uci/ics/__init__.py +++ b/core/amber/src/main/python/proto/edu/uci/ics/__init__.py @@ -13,4 +13,4 @@ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations -# under the License. \ No newline at end of file +# under the License. diff --git a/core/amber/src/main/python/proto/edu/uci/ics/amber/__init__.py b/core/amber/src/main/python/proto/edu/uci/ics/amber/__init__.py index d216be4ddc9..13a83393a91 100644 --- a/core/amber/src/main/python/proto/edu/uci/ics/amber/__init__.py +++ b/core/amber/src/main/python/proto/edu/uci/ics/amber/__init__.py @@ -13,4 +13,4 @@ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations -# under the License. \ No newline at end of file +# under the License. diff --git a/core/amber/src/main/python/proto/edu/uci/ics/amber/engine/__init__.py b/core/amber/src/main/python/proto/edu/uci/ics/amber/engine/__init__.py index d216be4ddc9..13a83393a91 100644 --- a/core/amber/src/main/python/proto/edu/uci/ics/amber/engine/__init__.py +++ b/core/amber/src/main/python/proto/edu/uci/ics/amber/engine/__init__.py @@ -13,4 +13,4 @@ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations -# under the License. \ No newline at end of file +# under the License. diff --git a/core/amber/src/main/python/proto/edu/uci/ics/amber/engine/architecture/__init__.py b/core/amber/src/main/python/proto/edu/uci/ics/amber/engine/architecture/__init__.py index d216be4ddc9..13a83393a91 100644 --- a/core/amber/src/main/python/proto/edu/uci/ics/amber/engine/architecture/__init__.py +++ b/core/amber/src/main/python/proto/edu/uci/ics/amber/engine/architecture/__init__.py @@ -13,4 +13,4 @@ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations -# under the License. \ No newline at end of file +# under the License. From bceae6400b0eff468a68d0eb21678500d4ef705b Mon Sep 17 00:00:00 2001 From: Xinyuan Lin Date: Sun, 15 Jun 2025 18:33:35 -0700 Subject: [PATCH 10/39] init --- .../src/assets/operator_images/LoopEnd.png | Bin 0 -> 5865 bytes .../src/assets/operator_images/LoopStart.png | Bin 0 -> 2138 bytes .../core/executor/OperatorExecutor.scala | 5 ++ .../uci/ics/amber/operator/LogicalOp.scala | 28 +++----- .../amber/operator/loop/LoopEndOpDesc.scala | 53 ++++++++++++++ .../amber/operator/loop/LoopEndOpExec.scala | 35 ++++++++++ .../amber/operator/loop/LoopStartOpDesc.scala | 65 +++++++++++++++++ .../amber/operator/loop/LoopStartOpExec.scala | 46 ++++++++++++ .../amber/operator/sleep/SleepOpDesc.scala | 66 ++++++++++++++++++ .../amber/operator/sleep/SleepOpExec.scala | 33 +++++++++ 10 files changed, 312 insertions(+), 19 deletions(-) create mode 100644 core/gui/src/assets/operator_images/LoopEnd.png create mode 100644 core/gui/src/assets/operator_images/LoopStart.png create mode 100644 core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/loop/LoopEndOpDesc.scala create mode 100644 core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/loop/LoopEndOpExec.scala create mode 100644 core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/loop/LoopStartOpDesc.scala create mode 100644 core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/loop/LoopStartOpExec.scala create mode 100644 core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/sleep/SleepOpDesc.scala create mode 100644 core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/sleep/SleepOpExec.scala diff --git a/core/gui/src/assets/operator_images/LoopEnd.png b/core/gui/src/assets/operator_images/LoopEnd.png new file mode 100644 index 0000000000000000000000000000000000000000..ee0f9ab6faccd328c102f214a7547d3e34d8359b GIT binary patch literal 5865 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 literal 0 HcmV?d00001 diff --git a/core/gui/src/assets/operator_images/LoopStart.png b/core/gui/src/assets/operator_images/LoopStart.png new file mode 100644 index 0000000000000000000000000000000000000000..7e5be023cdf6b64dd1bc140e5d75980455afeefb GIT binary patch literal 2138 zcmV-g2&MOlP)Px-6iGxuRCr$Pok@1vFbsy}TQ#e+JKw6wt+cDNN^a$bl4voq27m;>LBHSYqAkVY z$0rb+#7kG<@A`TD-M{p=3agGPM=EfMd@k!_*RSip?%sX$Fa2BAilA^a1?mO0>wb6l z?j5k2v68i*x_G;KNw`$Lu656Tc9Eo zuwW)j8SQ}zShIknU1*!20#9EcQB z6cz^>1a(cxTka+6Y@h~k>Vdf?xiG9J0X0-|fOc49G29qr3Tieum%s|bdO}cVHSgWK zRd`?mYB7L@zzV^7YEZqNBe1F-Ru$AD0sZM+llKS!)Id<7mwzo{Qv-F_Gg`;Ajf=V5 zcEPBP*Z_3}D+OT1L9Nzsk<(YzwwE$XlA1;kSW!?b@}lb|h!7T_9?%@2san@xK`cu5 z4NPGHDnVMNrYfLLkh*xZogYJ3fJ!!d(!e#T>u=Ub0F_K1J7MHlAR1IaI-00i9oPgi z9*BT73e;we!|nF0U1^@ zs7;RU1&&!a`tnAT$9|9)ZqY&gy58B9DER@RKu1jgs7DGB{-0nA5~$U$I4!jz2t$w; zqkgJLlynDUghNo9^B97VVtR@~AU#e0tKZ~t7t{tJ75|b-a~jdPl25tgMD{_gXRF?k zXh|dcz09jVg*qRav^@^AC+J29unB4*p2DAq9F9D=pW8RejG4((AV;*f{{Mr`-7oK{ z+EsgPgPL!YKX^Y6rKgkdgTv61*9U7K6zNnkYM3epFlN!gQDWcT~H&}?8PnHV1_x_cm~zVnY^)I>;EKn0{3-V~!vN)Oxq z1Xa7+7l8B!s6zQ=uyYedn!q}B{|`_Nx_vRvhYQil;ar^?Yf$$#9B^gF6_Z@Wbki%x ztAm4^J*sB6k1kr5l+3wnhXBsW4WL>Slvzs@udINoSG{+mM^%k8;7yegDI0JR1JDygFuPXM(N0BWhGCaCVhT#aw=V=z?de(toRjZVSEj!2b;lCwiOAI&NI40$xA|c}btX{twu`E7^XstQe;!h`;?wV2 z%Jo4-_kRvhHM@QHbw`UcQ|SIrz^bA*?VPi-I)#csRl~5VdQ*UDgxEX`@Jqdlp9+)We4}*#tzgGR)hetLR`( zU|H*=iJ9@^;87n?j-wUVwYy&yI3H%)=vntJ?akv=w?Xq4_}$+EM50(-LrH< z#{4!zlKpGYwa)zOG_vem6=HDBqaRts+|g(kR99Km4F_4&$W*8>sEl|tQ-hhs9_Vun z>gTnz5P+#bA;|?nZ4OexEW{zGgI7fekWRZ0s4d!pa$Jc7Dqv0LRAQSWb7U`2X@e|? zo0Q0)PVs8qDJuir)QU*(sEv*6XNeh_us{W@2^ces5|+~3$lf=OsRtia46fp6Wg4dG za4SL!HmH+;m@# zDsgnbrDHOvO~+D)^f3}JKy6IZ+tl4B?Q;+TwJ~8C^f8h$L2X1+(!N)UiejKPA}mF- zxHJ)4oya1?$hF7Y9dSk14)CI8Pk&?3lD+Fpmu$lmB4(g-(a!au8T+XF? z&3-<|(l-l&nh{o0IL%^I3J3n{a?L|445|^V{+yco^kdRID20W+G-h2trJ@70V|d`a zt^4IY5lN{uHH`wO6RauwE@t@doj)}B__y3}7UfZ?b`FmA)(Wr6Db&ar0hK8vsx4#E z73EQ58h~eJ1sL0ssI2 literal 0 HcmV?d00001 diff --git a/core/workflow-core/src/main/scala/edu/uci/ics/amber/core/executor/OperatorExecutor.scala b/core/workflow-core/src/main/scala/edu/uci/ics/amber/core/executor/OperatorExecutor.scala index a3ef8ea917c..16706a59c21 100644 --- a/core/workflow-core/src/main/scala/edu/uci/ics/amber/core/executor/OperatorExecutor.scala +++ b/core/workflow-core/src/main/scala/edu/uci/ics/amber/core/executor/OperatorExecutor.scala @@ -56,4 +56,9 @@ trait OperatorExecutor { def close(): Unit = {} + def reset(): Unit = { + close() + open() + } + } diff --git a/core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/LogicalOp.scala b/core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/LogicalOp.scala index b053ff929a9..5167492f66e 100644 --- a/core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/LogicalOp.scala +++ b/core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/LogicalOp.scala @@ -24,11 +24,7 @@ import com.fasterxml.jackson.annotation._ import com.kjetland.jackson.jsonSchema.annotations.JsonSchemaTitle import edu.uci.ics.amber.core.executor.OperatorExecutor import edu.uci.ics.amber.core.tuple.Schema -import edu.uci.ics.amber.core.virtualidentity.{ - ExecutionIdentity, - OperatorIdentity, - WorkflowIdentity -} +import edu.uci.ics.amber.core.virtualidentity.{ExecutionIdentity, OperatorIdentity, WorkflowIdentity} import edu.uci.ics.amber.core.workflow.WorkflowContext.{DEFAULT_EXECUTION_ID, DEFAULT_WORKFLOW_ID} import edu.uci.ics.amber.core.workflow.{PhysicalOp, PhysicalPlan, PortIdentity} import edu.uci.ics.amber.operator.aggregate.AggregateOpDesc @@ -39,22 +35,15 @@ import edu.uci.ics.amber.operator.distinct.DistinctOpDesc import edu.uci.ics.amber.operator.dummy.DummyOpDesc import edu.uci.ics.amber.operator.filter.SpecializedFilterOpDesc import edu.uci.ics.amber.operator.hashJoin.HashJoinOpDesc -import edu.uci.ics.amber.operator.huggingFace.{ - HuggingFaceIrisLogisticRegressionOpDesc, - HuggingFaceSentimentAnalysisOpDesc, - HuggingFaceSpamSMSDetectionOpDesc, - HuggingFaceTextSummarizationOpDesc -} +import edu.uci.ics.amber.operator.huggingFace.{HuggingFaceIrisLogisticRegressionOpDesc, HuggingFaceSentimentAnalysisOpDesc, HuggingFaceSpamSMSDetectionOpDesc, HuggingFaceTextSummarizationOpDesc} import edu.uci.ics.amber.operator.ifStatement.IfOpDesc import edu.uci.ics.amber.operator.intersect.IntersectOpDesc import edu.uci.ics.amber.operator.intervalJoin.IntervalJoinOpDesc import edu.uci.ics.amber.operator.keywordSearch.KeywordSearchOpDesc import edu.uci.ics.amber.operator.limit.LimitOpDesc +import edu.uci.ics.amber.operator.loop.{LoopEndOpDesc, LoopStartOpDesc} import edu.uci.ics.amber.operator.machineLearning.Scorer.MachineLearningScorerOpDesc -import edu.uci.ics.amber.operator.machineLearning.sklearnAdvanced.KNNTrainer.{ - SklearnAdvancedKNNClassifierTrainerOpDesc, - SklearnAdvancedKNNRegressorTrainerOpDesc -} +import edu.uci.ics.amber.operator.machineLearning.sklearnAdvanced.KNNTrainer.{SklearnAdvancedKNNClassifierTrainerOpDesc, SklearnAdvancedKNNRegressorTrainerOpDesc} import edu.uci.ics.amber.operator.machineLearning.sklearnAdvanced.SVCTrainer.SklearnAdvancedSVCTrainerOpDesc import edu.uci.ics.amber.operator.machineLearning.sklearnAdvanced.SVRTrainer.SklearnAdvancedSVRTrainerOpDesc import edu.uci.ics.amber.operator.metadata.{OPVersion, OperatorInfo, PropertyNameConstants} @@ -63,13 +52,11 @@ import edu.uci.ics.amber.operator.randomksampling.RandomKSamplingOpDesc import edu.uci.ics.amber.operator.regex.RegexOpDesc import edu.uci.ics.amber.operator.reservoirsampling.ReservoirSamplingOpDesc import edu.uci.ics.amber.operator.sklearn._ +import edu.uci.ics.amber.operator.sleep.SleepOpDesc import edu.uci.ics.amber.operator.sort.SortOpDesc import edu.uci.ics.amber.operator.sortPartitions.SortPartitionsOpDesc import edu.uci.ics.amber.operator.source.apis.reddit.RedditSearchSourceOpDesc -import edu.uci.ics.amber.operator.source.apis.twitter.v2.{ - TwitterFullArchiveSearchSourceOpDesc, - TwitterSearchSourceOpDesc -} +import edu.uci.ics.amber.operator.source.apis.twitter.v2.{TwitterFullArchiveSearchSourceOpDesc, TwitterSearchSourceOpDesc} import edu.uci.ics.amber.operator.source.fetcher.URLFetcherOpDesc import edu.uci.ics.amber.operator.source.scan.FileScanSourceOpDesc import edu.uci.ics.amber.operator.source.scan.arrow.ArrowSourceOpDesc @@ -185,6 +172,9 @@ trait StateTransferFunc new Type(value = classOf[AsterixDBSourceOpDesc], name = "AsterixDBSource"), new Type(value = classOf[TypeCastingOpDesc], name = "TypeCasting"), new Type(value = classOf[LimitOpDesc], name = "Limit"), + new Type(value = classOf[SleepOpDesc], name = "Sleep"), + new Type(value = classOf[LoopStartOpDesc], name = "LoopStart"), + new Type(value = classOf[LoopEndOpDesc], name = "LoopEnd"), new Type(value = classOf[RandomKSamplingOpDesc], name = "RandomKSampling"), new Type(value = classOf[ReservoirSamplingOpDesc], name = "ReservoirSampling"), new Type(value = classOf[HashJoinOpDesc[String]], name = "HashJoin"), diff --git a/core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/loop/LoopEndOpDesc.scala b/core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/loop/LoopEndOpDesc.scala new file mode 100644 index 00000000000..b7e743ead3c --- /dev/null +++ b/core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/loop/LoopEndOpDesc.scala @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package edu.uci.ics.amber.operator.loop + +import edu.uci.ics.amber.core.executor.OpExecWithClassName +import edu.uci.ics.amber.core.virtualidentity.{ExecutionIdentity, WorkflowIdentity} +import edu.uci.ics.amber.core.workflow.{InputPort, OutputPort, PhysicalOp} +import edu.uci.ics.amber.operator.metadata.{OperatorGroupConstants, OperatorInfo} +import edu.uci.ics.amber.operator.LogicalOp + +class LoopEndOpDesc extends LogicalOp { + override def getPhysicalOp( + workflowId: WorkflowIdentity, + executionId: ExecutionIdentity + ): PhysicalOp = { + PhysicalOp + .oneToOnePhysicalOp( + workflowId, + executionId, + operatorIdentifier, + OpExecWithClassName("edu.uci.ics.amber.operator.loop.LoopEndOpExec") + ) + .withInputPorts(operatorInfo.inputPorts) + .withOutputPorts(operatorInfo.outputPorts) + .withSuggestedWorkerNum(1) + } + + override def operatorInfo: OperatorInfo = + OperatorInfo( + "Loop End", + "Loop End", + OperatorGroupConstants.CONTROL_GROUP, + inputPorts = List(InputPort()), + outputPorts = List(OutputPort()) + ) +} diff --git a/core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/loop/LoopEndOpExec.scala b/core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/loop/LoopEndOpExec.scala new file mode 100644 index 00000000000..54f399eaccb --- /dev/null +++ b/core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/loop/LoopEndOpExec.scala @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package edu.uci.ics.amber.operator.loop + +import edu.uci.ics.amber.core.executor.OperatorExecutor +import edu.uci.ics.amber.core.tuple.{Tuple, TupleLike} +import scala.collection.mutable + +class LoopEndOpExec extends OperatorExecutor { + private val data = new mutable.ArrayBuffer[Tuple] + + override def processTuple(tuple: Tuple, port: Int): Iterator[TupleLike] = { + data.append(tuple) + Iterator.empty + } + + override def onFinish(port: Int): Iterator[TupleLike] = data.iterator +} diff --git a/core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/loop/LoopStartOpDesc.scala b/core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/loop/LoopStartOpDesc.scala new file mode 100644 index 00000000000..1a8db3a3eed --- /dev/null +++ b/core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/loop/LoopStartOpDesc.scala @@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package edu.uci.ics.amber.operator.loop + +import com.fasterxml.jackson.annotation.JsonProperty +import com.kjetland.jackson.jsonSchema.annotations.JsonSchemaTitle +import edu.uci.ics.amber.core.executor.OpExecWithClassName +import edu.uci.ics.amber.core.virtualidentity.{ExecutionIdentity, WorkflowIdentity} +import edu.uci.ics.amber.core.workflow.{InputPort, OutputPort, PhysicalOp} +import edu.uci.ics.amber.operator.LogicalOp +import edu.uci.ics.amber.operator.metadata.{OperatorGroupConstants, OperatorInfo} +import edu.uci.ics.amber.util.JSONUtils.objectMapper + +class LoopStartOpDesc extends LogicalOp { + + @JsonProperty(required = true) + @JsonSchemaTitle("Iteration Number") + var iteration: Int = _ + + override def getPhysicalOp( + workflowId: WorkflowIdentity, + executionId: ExecutionIdentity + ): PhysicalOp = { + PhysicalOp + .oneToOnePhysicalOp( + workflowId, + executionId, + operatorIdentifier, + OpExecWithClassName( + "edu.uci.ics.amber.operator.loop.LoopStartOpExec", + objectMapper.writeValueAsString(this) + ) + ) + .withInputPorts(operatorInfo.inputPorts) + .withOutputPorts(operatorInfo.outputPorts) + .withSuggestedWorkerNum(1) + } + + override def operatorInfo: OperatorInfo = + OperatorInfo( + "Loop Start", + "Loop Start", + OperatorGroupConstants.CONTROL_GROUP, + inputPorts = List(InputPort()), + outputPorts = List(OutputPort()) + ) + +} diff --git a/core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/loop/LoopStartOpExec.scala b/core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/loop/LoopStartOpExec.scala new file mode 100644 index 00000000000..9128bc185ea --- /dev/null +++ b/core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/loop/LoopStartOpExec.scala @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package edu.uci.ics.amber.operator.loop + +import edu.uci.ics.amber.core.executor.OperatorExecutor +import edu.uci.ics.amber.core.tuple.{Tuple, TupleLike} +import edu.uci.ics.amber.util.JSONUtils.objectMapper +import scala.collection.mutable + +class LoopStartOpExec(descString: String) extends OperatorExecutor { + private val desc: LoopStartOpDesc = objectMapper.readValue(descString, classOf[LoopStartOpDesc]) + private val data = new mutable.ArrayBuffer[Tuple] + private var currentIteration = 0 + + + def checkCondition(): Boolean = { + desc.iteration>currentIteration + } + + override def processTuple(tuple: Tuple, port: Int): Iterator[TupleLike] = { + data.append(tuple) + Iterator.empty + } + + override def onFinish(port: Int): Iterator[TupleLike] = { + currentIteration += 1 + data.iterator + } +} \ No newline at end of file diff --git a/core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/sleep/SleepOpDesc.scala b/core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/sleep/SleepOpDesc.scala new file mode 100644 index 00000000000..f357c9cf084 --- /dev/null +++ b/core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/sleep/SleepOpDesc.scala @@ -0,0 +1,66 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package edu.uci.ics.amber.operator.sleep + +import com.fasterxml.jackson.annotation.JsonProperty +import com.kjetland.jackson.jsonSchema.annotations.JsonSchemaTitle +import edu.uci.ics.amber.core.executor.OpExecWithClassName +import edu.uci.ics.amber.core.virtualidentity.{ExecutionIdentity, WorkflowIdentity} +import edu.uci.ics.amber.core.workflow.{InputPort, OutputPort, PhysicalOp} +import edu.uci.ics.amber.operator.LogicalOp +import edu.uci.ics.amber.operator.metadata.{OperatorGroupConstants, OperatorInfo} +import edu.uci.ics.amber.util.JSONUtils.objectMapper + +class SleepOpDesc extends LogicalOp { + + @JsonProperty(required = true) + @JsonSchemaTitle("Sleep") + var time: Int = _ + + override def getPhysicalOp( + workflowId: WorkflowIdentity, + executionId: ExecutionIdentity + ): PhysicalOp = { + PhysicalOp + .oneToOnePhysicalOp( + workflowId, + executionId, + operatorIdentifier, + OpExecWithClassName( + "edu.uci.ics.amber.operator.sleep.SleepOpExec", + objectMapper.writeValueAsString(this) + ) + ) + .withInputPorts(operatorInfo.inputPorts) + .withOutputPorts(operatorInfo.outputPorts) + .withParallelizable(false) + .withSuggestedWorkerNum(1) + + } + + override def operatorInfo: OperatorInfo = + OperatorInfo( + "Sleep", + "Limit the number of output rows", + OperatorGroupConstants.CONTROL_GROUP, + inputPorts = List(InputPort()), + outputPorts = List(OutputPort()), + ) +} \ No newline at end of file diff --git a/core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/sleep/SleepOpExec.scala b/core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/sleep/SleepOpExec.scala new file mode 100644 index 00000000000..f6630710508 --- /dev/null +++ b/core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/sleep/SleepOpExec.scala @@ -0,0 +1,33 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package edu.uci.ics.amber.operator.sleep + +import edu.uci.ics.amber.core.executor.OperatorExecutor +import edu.uci.ics.amber.core.tuple.{Tuple, TupleLike} +import edu.uci.ics.amber.util.JSONUtils.objectMapper + +class SleepOpExec(descString: String) extends OperatorExecutor { + private val desc: SleepOpDesc = objectMapper.readValue(descString, classOf[SleepOpDesc]) + + override def processTuple(tuple: Tuple, port: Int): Iterator[TupleLike] = { + Thread.sleep(1000*desc.time) + Iterator(tuple) + } +} From 7c257a7c95922221ad9e86b51885b6d3acfa7e3e Mon Sep 17 00:00:00 2001 From: Xinyuan Lin Date: Sun, 15 Jun 2025 19:32:52 -0700 Subject: [PATCH 11/39] rename to is_ecm_aligned --- .../architecture/managers/embedded_control_message_manager.py | 2 +- core/amber/src/main/python/core/runnables/main_loop.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/amber/src/main/python/core/architecture/managers/embedded_control_message_manager.py b/core/amber/src/main/python/core/architecture/managers/embedded_control_message_manager.py index e10ad2c939d..ea01b24b40f 100644 --- a/core/amber/src/main/python/core/architecture/managers/embedded_control_message_manager.py +++ b/core/amber/src/main/python/core/architecture/managers/embedded_control_message_manager.py @@ -32,7 +32,7 @@ def __init__(self, actor_id: ActorVirtualIdentity, input_gateway): self.input_gateway = input_gateway self.ecm_received: Dict[str, Set[ChannelIdentity]] = defaultdict(set) - def ecm_aligned( + def is_ecm_aligned( self, from_channel: ChannelIdentity, ecm: EmbeddedControlMessage ) -> bool: """ diff --git a/core/amber/src/main/python/core/runnables/main_loop.py b/core/amber/src/main/python/core/runnables/main_loop.py index ec78b7b16ea..111f0e07e8b 100644 --- a/core/amber/src/main/python/core/runnables/main_loop.py +++ b/core/amber/src/main/python/core/runnables/main_loop.py @@ -315,7 +315,7 @@ def _process_ecm(self, ecm_element: EmbeddedControlMessageElement): PauseType.ECM_PAUSE, channel_id ) - if self.context.ecm_manager.ecm_aligned(channel_id, ecm): + if self.context.ecm_manager.is_ecm_aligned(channel_id, ecm): logger.info( f"process channel ECM from {channel_id}," f" id = {ecm.id}, cmd = {command}" From 64b5fc0cccf4c9cb3a27b6c98e5cc673b026ead0 Mon Sep 17 00:00:00 2001 From: Xinyuan Lin Date: Sun, 15 Jun 2025 19:33:56 -0700 Subject: [PATCH 12/39] rename to _send_ecm_to_channel --- core/amber/src/main/python/core/runnables/main_loop.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/amber/src/main/python/core/runnables/main_loop.py b/core/amber/src/main/python/core/runnables/main_loop.py index 111f0e07e8b..d5ea6522366 100644 --- a/core/amber/src/main/python/core/runnables/main_loop.py +++ b/core/amber/src/main/python/core/runnables/main_loop.py @@ -338,7 +338,7 @@ def _process_ecm(self, ecm_element: EmbeddedControlMessageElement): f"send ECM to {active_channel_id}," f" id = {ecm.id}, cmd = {command}" ) - self._send_ecm(active_channel_id, ecm) + self._send_ecm_to_channel(active_channel_id, ecm) if ecm.ecm_type != EmbeddedControlMessageType.NO_ALIGNMENT: self.context.pause_manager.resume(PauseType.ECM_PAUSE) @@ -369,9 +369,9 @@ def _send_ecm_to_data_channels( ) }, ) - self._send_ecm(active_channel_id, ecm) + self._send_ecm_to_channel(active_channel_id, ecm) - def _send_ecm( + def _send_ecm_to_channel( self, channel_id: ChannelIdentity, ecm: EmbeddedControlMessage ) -> None: for batch in self.context.output_manager.emit_ecm(channel_id.to_worker_id, ecm): From 62c6877a4d97099e11d3160ea9285c58dff4d6ea Mon Sep 17 00:00:00 2001 From: Xinyuan Lin Date: Sun, 15 Jun 2025 19:35:36 -0700 Subject: [PATCH 13/39] rename to isECMAligned --- .../ics/amber/engine/architecture/worker/DataProcessor.scala | 2 +- .../architecture/worker/EmbeddedControlMessageManager.scala | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/DataProcessor.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/DataProcessor.scala index 346da8dd552..d3a57dc5f1b 100644 --- a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/DataProcessor.scala +++ b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/DataProcessor.scala @@ -241,7 +241,7 @@ class DataProcessor( if (ecm.ecmType != NO_ALIGNMENT) { pauseManager.pauseInputChannel(ECMPause(ecm.id), List(channelId)) } - if (ecmManager.ecmAligned(channelId, ecm)) { + if (ecmManager.isECMAligned(channelId, ecm)) { logManager.markAsReplayDestination(ecm.id) // invoke the control command carried with the ECM logger.info(s"process ECM from $channelId, id = ${ecm.id}, cmd = $command") diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/EmbeddedControlMessageManager.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/EmbeddedControlMessageManager.scala index cfdce52d76d..55d3cbc37fd 100644 --- a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/EmbeddedControlMessageManager.scala +++ b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/EmbeddedControlMessageManager.scala @@ -55,7 +55,7 @@ class EmbeddedControlMessageManager( * @return Boolean indicating if the ECM is completely received from all senders * within the scope. Returns true if the ECM is aligned, otherwise false. */ - def ecmAligned( + def isECMAligned( from: ChannelIdentity, ecm: EmbeddedControlMessage ): Boolean = { From 8d910a35adae1aeb5db9064621f86d9056643c83 Mon Sep 17 00:00:00 2001 From: Xinyuan Lin Date: Sun, 15 Jun 2025 19:36:04 -0700 Subject: [PATCH 14/39] rename to sendECMToChannel --- .../ics/amber/engine/architecture/worker/DataProcessor.scala | 2 +- .../edu/uci/ics/amber/engine/common/rpc/AsyncRPCClient.scala | 2 +- .../amber/engine/common/rpc/AsyncRPCHandlerInitializer.scala | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/DataProcessor.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/DataProcessor.scala index d3a57dc5f1b..e4674f7a339 100644 --- a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/DataProcessor.scala +++ b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/DataProcessor.scala @@ -276,7 +276,7 @@ class DataProcessor( outputGateway.getActiveChannels .filter(!_.isControl) .foreach { activeChannelId => - asyncRPCClient.sendECM( + asyncRPCClient.sendECMToChannel( EmbeddedControlMessageIdentity(method.getBareMethodName), alignment, Set(), diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/common/rpc/AsyncRPCClient.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/common/rpc/AsyncRPCClient.scala index a7e293cbb6f..2fbe50d992f 100644 --- a/core/amber/src/main/scala/edu/uci/ics/amber/engine/common/rpc/AsyncRPCClient.scala +++ b/core/amber/src/main/scala/edu/uci/ics/amber/engine/common/rpc/AsyncRPCClient.scala @@ -154,7 +154,7 @@ class AsyncRPCClient( (ControlInvocation(methodName, message, context, pid), p) } - def sendECM( + def sendECMToChannel( ecmId: EmbeddedControlMessageIdentity, ecmType: EmbeddedControlMessageType, scope: Set[ChannelIdentity], diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/common/rpc/AsyncRPCHandlerInitializer.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/common/rpc/AsyncRPCHandlerInitializer.scala index 5d62a3f911f..9015ca671cd 100644 --- a/core/amber/src/main/scala/edu/uci/ics/amber/engine/common/rpc/AsyncRPCHandlerInitializer.scala +++ b/core/amber/src/main/scala/edu/uci/ics/amber/engine/common/rpc/AsyncRPCHandlerInitializer.scala @@ -62,7 +62,7 @@ class AsyncRPCHandlerInitializer( cmdMapping: Map[String, ControlInvocation], to: ChannelIdentity ): Unit = { - ctrlSource.sendECM(ecmId, ecmType, scope, cmdMapping, to) + ctrlSource.sendECMToChannel(ecmId, ecmType, scope, cmdMapping, to) } def sendToClient(clientEvent: ClientEvent): Unit = { From 056ab9186d6ee4b47c026f9ca31673112d62a4f6 Mon Sep 17 00:00:00 2001 From: Xinyuan Lin Date: Mon, 16 Jun 2025 19:04:05 -0700 Subject: [PATCH 15/39] fix fmt --- .../worker/promisehandlers/EndChannelHandler.scala | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/EndChannelHandler.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/EndChannelHandler.scala index 863dd59f5c7..19564d5c532 100644 --- a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/EndChannelHandler.scala +++ b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/EndChannelHandler.scala @@ -25,6 +25,7 @@ import edu.uci.ics.amber.engine.architecture.rpc.controlcommands.{AsyncRPCContex import edu.uci.ics.amber.engine.architecture.rpc.controlreturns.EmptyReturn import edu.uci.ics.amber.engine.architecture.worker.DataProcessorRPCHandlerInitializer import edu.uci.ics.amber.error.ErrorUtils.safely +import edu.uci.ics.amber.operator.loop.LoopStartOpExec trait EndChannelHandler { this: DataProcessorRPCHandlerInitializer => @@ -59,8 +60,13 @@ trait EndChannelHandler { // Need this check for handling input port dependency relationships. // See documentation of isMissingOutputPort if (!dp.outputManager.isMissingOutputPort) { - // assuming all the output ports finalize after all input ports are finalized. - dp.outputManager.finalizeOutput() + dp.executor match { + case _: LoopStartOpExec => + dp.outputManager.emitMarker(EndOfIteration(actorId)) + case _ => + // assuming all the output ports finalize after all input ports are finalized. + dp.outputManager.finalizeOutput() + } } } EmptyReturn() From 89276d474227f7954cb894adb5e1496938b32852 Mon Sep 17 00:00:00 2001 From: Xinyuan Lin Date: Mon, 16 Jun 2025 19:15:21 -0700 Subject: [PATCH 16/39] fix fmt --- .../ics/amber/engine/architecture/rpc/controlcommands.proto | 4 ++++ .../uci/ics/amber/engine/architecture/rpc/workerservice.proto | 1 + .../worker/promisehandlers/EndChannelHandler.scala | 4 ++-- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/core/amber/src/main/protobuf/edu/uci/ics/amber/engine/architecture/rpc/controlcommands.proto b/core/amber/src/main/protobuf/edu/uci/ics/amber/engine/architecture/rpc/controlcommands.proto index 51a95379574..171589eece7 100644 --- a/core/amber/src/main/protobuf/edu/uci/ics/amber/engine/architecture/rpc/controlcommands.proto +++ b/core/amber/src/main/protobuf/edu/uci/ics/amber/engine/architecture/rpc/controlcommands.proto @@ -271,4 +271,8 @@ message PrepareCheckpointRequest{ message QueryStatisticsRequest{ repeated core.ActorVirtualIdentity filterByWorkers = 1; +} + +message EndIterationRequest{ + core.ActorVirtualIdentity worker = 1; } \ No newline at end of file diff --git a/core/amber/src/main/protobuf/edu/uci/ics/amber/engine/architecture/rpc/workerservice.proto b/core/amber/src/main/protobuf/edu/uci/ics/amber/engine/architecture/rpc/workerservice.proto index cb99bf234be..4234f19c437 100644 --- a/core/amber/src/main/protobuf/edu/uci/ics/amber/engine/architecture/rpc/workerservice.proto +++ b/core/amber/src/main/protobuf/edu/uci/ics/amber/engine/architecture/rpc/workerservice.proto @@ -46,6 +46,7 @@ service WorkerService { rpc StartWorker(EmptyRequest) returns (WorkerStateResponse); rpc StartChannel(EmptyRequest) returns (EmptyReturn); rpc EndChannel(EmptyRequest) returns (EmptyReturn); + rpc EndIteration(EndIterationRequest) returns (EmptyReturn); rpc DebugCommand(DebugCommandRequest) returns (EmptyReturn); rpc EvaluatePythonExpression(EvaluatePythonExpressionRequest) returns (EvaluatedValue); rpc NoOperation(EmptyRequest) returns (EmptyReturn); diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/EndChannelHandler.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/EndChannelHandler.scala index 19564d5c532..5b67c07d631 100644 --- a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/EndChannelHandler.scala +++ b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/EndChannelHandler.scala @@ -61,8 +61,8 @@ trait EndChannelHandler { // See documentation of isMissingOutputPort if (!dp.outputManager.isMissingOutputPort) { dp.executor match { - case _: LoopStartOpExec => - dp.outputManager.emitMarker(EndOfIteration(actorId)) + //case _: LoopStartOpExec => + //dp.sendECMToDataChannels(EndOfIteration(actorId)) case _ => // assuming all the output ports finalize after all input ports are finalized. dp.outputManager.finalizeOutput() From 82bde54378bbea1299c9ab0f78913e5bb2cc831f Mon Sep 17 00:00:00 2001 From: Xinyuan Lin Date: Mon, 16 Jun 2025 20:05:11 -0700 Subject: [PATCH 17/39] update --- .../engine/architecture/rpc/controlcommands.proto | 3 ++- .../engine/architecture/worker/DataProcessor.scala | 13 +++++++------ .../worker/promisehandlers/EndChannelHandler.scala | 9 ++++++--- .../promisehandlers/StartChannelHandler.scala | 2 +- 4 files changed, 16 insertions(+), 11 deletions(-) diff --git a/core/amber/src/main/protobuf/edu/uci/ics/amber/engine/architecture/rpc/controlcommands.proto b/core/amber/src/main/protobuf/edu/uci/ics/amber/engine/architecture/rpc/controlcommands.proto index 171589eece7..beafb170e7d 100644 --- a/core/amber/src/main/protobuf/edu/uci/ics/amber/engine/architecture/rpc/controlcommands.proto +++ b/core/amber/src/main/protobuf/edu/uci/ics/amber/engine/architecture/rpc/controlcommands.proto @@ -57,6 +57,7 @@ message ControlRequest { EmptyRequest emptyRequest = 56; PrepareCheckpointRequest prepareCheckpointRequest = 57; QueryStatisticsRequest queryStatisticsRequest = 58; + EndIterationRequest endIterationRequest = 59; // request for testing Ping ping = 100; @@ -274,5 +275,5 @@ message QueryStatisticsRequest{ } message EndIterationRequest{ - core.ActorVirtualIdentity worker = 1; + core.ActorVirtualIdentity worker = 1 [(scalapb.field).no_box = true]; } \ No newline at end of file diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/DataProcessor.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/DataProcessor.scala index e4674f7a339..467b0641b0e 100644 --- a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/DataProcessor.scala +++ b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/DataProcessor.scala @@ -163,7 +163,7 @@ class DataProcessor( if (outputTuple == null) return outputTuple match { case FinalizeExecutor() => - sendECMToDataChannels(METHOD_END_CHANNEL, PORT_ALIGNMENT) + sendECMToDataChannels(METHOD_END_CHANNEL.getBareMethodName, PORT_ALIGNMENT) // Send Completed signal to worker actor. executor.close() adaptiveBatchingMonitor.stopAdaptiveBatching() @@ -269,22 +269,23 @@ class DataProcessor( } def sendECMToDataChannels( - method: MethodDescriptor[EmptyRequest, EmptyReturn], - alignment: EmbeddedControlMessageType + method: String, + alignment: EmbeddedControlMessageType, + request: ControlRequest = EmptyRequest() ): Unit = { outputManager.flush() outputGateway.getActiveChannels .filter(!_.isControl) .foreach { activeChannelId => asyncRPCClient.sendECMToChannel( - EmbeddedControlMessageIdentity(method.getBareMethodName), + EmbeddedControlMessageIdentity(method), alignment, Set(), Map( activeChannelId.toWorkerId.name -> ControlInvocation( - method.getBareMethodName, - EmptyRequest(), + method, + request, AsyncRPCContext(ActorVirtualIdentity(""), ActorVirtualIdentity("")), -1 ) diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/EndChannelHandler.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/EndChannelHandler.scala index 5b67c07d631..c69c0cb1c44 100644 --- a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/EndChannelHandler.scala +++ b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/EndChannelHandler.scala @@ -21,8 +21,10 @@ package edu.uci.ics.amber.engine.architecture.worker.promisehandlers import com.twitter.util.Future import edu.uci.ics.amber.core.tuple.FinalizePort -import edu.uci.ics.amber.engine.architecture.rpc.controlcommands.{AsyncRPCContext, EmptyRequest} +import edu.uci.ics.amber.engine.architecture.rpc.controlcommands.EmbeddedControlMessageType.PORT_ALIGNMENT +import edu.uci.ics.amber.engine.architecture.rpc.controlcommands.{AsyncRPCContext, EmptyRequest, EndIterationRequest} import edu.uci.ics.amber.engine.architecture.rpc.controlreturns.EmptyReturn +import edu.uci.ics.amber.engine.architecture.rpc.workerservice.WorkerServiceGrpc.METHOD_END_ITERATION import edu.uci.ics.amber.engine.architecture.worker.DataProcessorRPCHandlerInitializer import edu.uci.ics.amber.error.ErrorUtils.safely import edu.uci.ics.amber.operator.loop.LoopStartOpExec @@ -61,8 +63,9 @@ trait EndChannelHandler { // See documentation of isMissingOutputPort if (!dp.outputManager.isMissingOutputPort) { dp.executor match { - //case _: LoopStartOpExec => - //dp.sendECMToDataChannels(EndOfIteration(actorId)) + case _: LoopStartOpExec => + dp.sendECMToDataChannels(METHOD_END_ITERATION.getBareMethodName, PORT_ALIGNMENT, + EndIterationRequest(dp.actorId)) case _ => // assuming all the output ports finalize after all input ports are finalized. dp.outputManager.finalizeOutput() diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/StartChannelHandler.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/StartChannelHandler.scala index 36918015388..fe38d7aff85 100644 --- a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/StartChannelHandler.scala +++ b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/StartChannelHandler.scala @@ -35,7 +35,7 @@ trait StartChannelHandler { ctx: AsyncRPCContext ): Future[EmptyReturn] = { val portId = dp.inputGateway.getChannel(dp.inputManager.currentChannelId).getPortId - dp.sendECMToDataChannels(METHOD_START_CHANNEL, NO_ALIGNMENT) + dp.sendECMToDataChannels(METHOD_START_CHANNEL.getBareMethodName, NO_ALIGNMENT) try { val outputState = dp.executor.produceStateOnStart(portId.id) if (outputState.isDefined) { From 34132fc59a0f1ea0de3f429854150dc73e37cdfa Mon Sep 17 00:00:00 2001 From: Xinyuan Lin Date: Sat, 21 Jun 2025 22:57:28 -0700 Subject: [PATCH 18/39] UPDATE --- .../pythonworker/PythonProxyClient.scala | 1 - .../DataProcessorRPCHandlerInitializer.scala | 1 + .../promisehandlers/EndIterationHandler.scala | 81 +++++++++++++++++++ 3 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/EndIterationHandler.scala diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/pythonworker/PythonProxyClient.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/pythonworker/PythonProxyClient.scala index 0fee53c01a8..c7f253b136b 100644 --- a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/pythonworker/PythonProxyClient.scala +++ b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/pythonworker/PythonProxyClient.scala @@ -21,7 +21,6 @@ package edu.uci.ics.amber.engine.architecture.pythonworker import com.twitter.util.{Await, Promise} import edu.uci.ics.amber.core.WorkflowRuntimeException -import edu.uci.ics.amber.core.state.State import edu.uci.ics.amber.core.tuple.{Schema, Tuple} import edu.uci.ics.amber.core.virtualidentity.{ActorVirtualIdentity, ChannelIdentity} import edu.uci.ics.amber.engine.architecture.pythonworker.WorkerBatchInternalQueue.{ diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/DataProcessorRPCHandlerInitializer.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/DataProcessorRPCHandlerInitializer.scala index 442372a482a..b2c104ba2c2 100644 --- a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/DataProcessorRPCHandlerInitializer.scala +++ b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/DataProcessorRPCHandlerInitializer.scala @@ -46,6 +46,7 @@ class DataProcessorRPCHandlerInitializer(val dp: DataProcessor) with StartHandler with StartChannelHandler with EndChannelHandler + with EndIterationHandler with AssignPortHandler with AddInputChannelHandler with FlushNetworkBufferHandler diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/EndIterationHandler.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/EndIterationHandler.scala new file mode 100644 index 00000000000..64ffb86ead6 --- /dev/null +++ b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/EndIterationHandler.scala @@ -0,0 +1,81 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package edu.uci.ics.amber.engine.architecture.worker.promisehandlers + +import com.twitter.util.Future +import edu.uci.ics.amber.core.tuple.FinalizePort +import edu.uci.ics.amber.engine.architecture.rpc.controlcommands.EmbeddedControlMessageType.PORT_ALIGNMENT +import edu.uci.ics.amber.engine.architecture.rpc.controlcommands.{ + AsyncRPCContext, + EmptyRequest, + EndIterationRequest +} +import edu.uci.ics.amber.engine.architecture.rpc.controlreturns.EmptyReturn +import edu.uci.ics.amber.engine.architecture.rpc.workerservice.WorkerServiceGrpc.METHOD_END_ITERATION +import edu.uci.ics.amber.engine.architecture.worker.DataProcessorRPCHandlerInitializer +import edu.uci.ics.amber.error.ErrorUtils.safely +import edu.uci.ics.amber.operator.loop.LoopStartOpExec + +trait EndIterationHandler { + this: DataProcessorRPCHandlerInitializer => + + override def endIteration( + request: EndIterationRequest, + ctx: AsyncRPCContext + ): Future[EmptyReturn] = { + val channelId = dp.inputManager.currentChannelId + val portId = dp.inputGateway.getChannel(channelId).getPortId + dp.inputManager.getPort(portId).completed = true + dp.inputManager.initBatch(channelId, Array.empty) + try { + val outputState = dp.executor.produceStateOnFinish(portId.id) + if (outputState.isDefined) { + dp.outputManager.emitState(outputState.get) + } + dp.outputManager.outputIterator.setTupleOutput( + dp.executor.onFinishMultiPort(portId.id) + ) + } catch safely { + case e => + // forward input tuple to the user and pause DP thread + dp.handleExecutorException(e) + } + + dp.outputManager.outputIterator.appendSpecialTupleToEnd( + FinalizePort(portId, input = true) + ) + + if (dp.inputManager.getAllPorts.forall(portId => dp.inputManager.isPortCompleted(portId))) { + // Need this check for handling input port dependency relationships. + // See documentation of isMissingOutputPort + if (!dp.outputManager.isMissingOutputPort) { + dp.executor match { + case _: LoopStartOpExec => + dp.sendECMToDataChannels(METHOD_END_ITERATION.getBareMethodName, PORT_ALIGNMENT, + EndIterationRequest(dp.actorId)) + case _ => + // assuming all the output ports finalize after all input ports are finalized. + dp.outputManager.finalizeOutput() + } + } + } + EmptyReturn() + } +} From 7ef95a42f39704d82c4843169b6f56426cdd09fd Mon Sep 17 00:00:00 2001 From: Xinyuan Lin Date: Sun, 22 Jun 2025 05:13:17 -0700 Subject: [PATCH 19/39] done! --- .../architecture/rpc/workerservice.proto | 1 + .../DataProcessorRPCHandlerInitializer.scala | 1 + .../promisehandlers/EndChannelHandler.scala | 3 +- .../promisehandlers/EndIterationHandler.scala | 51 +++---------------- .../NextIterationHandler.scala | 51 +++++++++++++++++++ 5 files changed, 62 insertions(+), 45 deletions(-) create mode 100644 core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/NextIterationHandler.scala diff --git a/core/amber/src/main/protobuf/edu/uci/ics/amber/engine/architecture/rpc/workerservice.proto b/core/amber/src/main/protobuf/edu/uci/ics/amber/engine/architecture/rpc/workerservice.proto index 4234f19c437..73f0eea35af 100644 --- a/core/amber/src/main/protobuf/edu/uci/ics/amber/engine/architecture/rpc/workerservice.proto +++ b/core/amber/src/main/protobuf/edu/uci/ics/amber/engine/architecture/rpc/workerservice.proto @@ -47,6 +47,7 @@ service WorkerService { rpc StartChannel(EmptyRequest) returns (EmptyReturn); rpc EndChannel(EmptyRequest) returns (EmptyReturn); rpc EndIteration(EndIterationRequest) returns (EmptyReturn); + rpc NextIteration(EmptyRequest) returns (EmptyReturn); rpc DebugCommand(DebugCommandRequest) returns (EmptyReturn); rpc EvaluatePythonExpression(EvaluatePythonExpressionRequest) returns (EvaluatedValue); rpc NoOperation(EmptyRequest) returns (EmptyReturn); diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/DataProcessorRPCHandlerInitializer.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/DataProcessorRPCHandlerInitializer.scala index b2c104ba2c2..637847bbcb3 100644 --- a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/DataProcessorRPCHandlerInitializer.scala +++ b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/DataProcessorRPCHandlerInitializer.scala @@ -47,6 +47,7 @@ class DataProcessorRPCHandlerInitializer(val dp: DataProcessor) with StartChannelHandler with EndChannelHandler with EndIterationHandler + with NextIterationHandler with AssignPortHandler with AddInputChannelHandler with FlushNetworkBufferHandler diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/EndChannelHandler.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/EndChannelHandler.scala index c69c0cb1c44..4cdff3d8325 100644 --- a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/EndChannelHandler.scala +++ b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/EndChannelHandler.scala @@ -64,8 +64,7 @@ trait EndChannelHandler { if (!dp.outputManager.isMissingOutputPort) { dp.executor match { case _: LoopStartOpExec => - dp.sendECMToDataChannels(METHOD_END_ITERATION.getBareMethodName, PORT_ALIGNMENT, - EndIterationRequest(dp.actorId)) + dp.sendECMToDataChannels(METHOD_END_ITERATION.getBareMethodName, PORT_ALIGNMENT, EndIterationRequest(dp.actorId)) case _ => // assuming all the output ports finalize after all input ports are finalized. dp.outputManager.finalizeOutput() diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/EndIterationHandler.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/EndIterationHandler.scala index 64ffb86ead6..faacf364cfe 100644 --- a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/EndIterationHandler.scala +++ b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/EndIterationHandler.scala @@ -20,18 +20,12 @@ package edu.uci.ics.amber.engine.architecture.worker.promisehandlers import com.twitter.util.Future -import edu.uci.ics.amber.core.tuple.FinalizePort import edu.uci.ics.amber.engine.architecture.rpc.controlcommands.EmbeddedControlMessageType.PORT_ALIGNMENT -import edu.uci.ics.amber.engine.architecture.rpc.controlcommands.{ - AsyncRPCContext, - EmptyRequest, - EndIterationRequest -} +import edu.uci.ics.amber.engine.architecture.rpc.controlcommands.{AsyncRPCContext, EmptyRequest, EndIterationRequest} import edu.uci.ics.amber.engine.architecture.rpc.controlreturns.EmptyReturn import edu.uci.ics.amber.engine.architecture.rpc.workerservice.WorkerServiceGrpc.METHOD_END_ITERATION import edu.uci.ics.amber.engine.architecture.worker.DataProcessorRPCHandlerInitializer -import edu.uci.ics.amber.error.ErrorUtils.safely -import edu.uci.ics.amber.operator.loop.LoopStartOpExec +import edu.uci.ics.amber.operator.loop.LoopEndOpExec trait EndIterationHandler { this: DataProcessorRPCHandlerInitializer => @@ -40,41 +34,12 @@ trait EndIterationHandler { request: EndIterationRequest, ctx: AsyncRPCContext ): Future[EmptyReturn] = { - val channelId = dp.inputManager.currentChannelId - val portId = dp.inputGateway.getChannel(channelId).getPortId - dp.inputManager.getPort(portId).completed = true - dp.inputManager.initBatch(channelId, Array.empty) - try { - val outputState = dp.executor.produceStateOnFinish(portId.id) - if (outputState.isDefined) { - dp.outputManager.emitState(outputState.get) - } - dp.outputManager.outputIterator.setTupleOutput( - dp.executor.onFinishMultiPort(portId.id) - ) - } catch safely { - case e => - // forward input tuple to the user and pause DP thread - dp.handleExecutorException(e) - } - - dp.outputManager.outputIterator.appendSpecialTupleToEnd( - FinalizePort(portId, input = true) - ) - - if (dp.inputManager.getAllPorts.forall(portId => dp.inputManager.isPortCompleted(portId))) { - // Need this check for handling input port dependency relationships. - // See documentation of isMissingOutputPort - if (!dp.outputManager.isMissingOutputPort) { - dp.executor match { - case _: LoopStartOpExec => - dp.sendECMToDataChannels(METHOD_END_ITERATION.getBareMethodName, PORT_ALIGNMENT, - EndIterationRequest(dp.actorId)) - case _ => - // assuming all the output ports finalize after all input ports are finalized. - dp.outputManager.finalizeOutput() - } - } + dp.executor match { + case _: LoopEndOpExec => + workerInterface.nextIteration(EmptyRequest(), mkContext(request.worker)) + case _ => + dp.executor.reset() + dp.sendECMToDataChannels(METHOD_END_ITERATION.getBareMethodName, PORT_ALIGNMENT, request) } EmptyReturn() } diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/NextIterationHandler.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/NextIterationHandler.scala new file mode 100644 index 00000000000..b6f13abde2f --- /dev/null +++ b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/NextIterationHandler.scala @@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package edu.uci.ics.amber.engine.architecture.worker.promisehandlers + +import com.twitter.util.Future +import edu.uci.ics.amber.engine.architecture.rpc.controlcommands.EmbeddedControlMessageType.PORT_ALIGNMENT +import edu.uci.ics.amber.engine.architecture.rpc.controlcommands.{AsyncRPCContext, EmptyRequest, EndIterationRequest} +import edu.uci.ics.amber.engine.architecture.rpc.controlreturns.EmptyReturn +import edu.uci.ics.amber.engine.architecture.rpc.workerservice.WorkerServiceGrpc.METHOD_END_ITERATION +import edu.uci.ics.amber.engine.architecture.worker.DataProcessorRPCHandlerInitializer +import edu.uci.ics.amber.operator.loop.{LoopEndOpExec, LoopStartOpExec} + +trait NextIterationHandler { + this: DataProcessorRPCHandlerInitializer => + + override def nextIteration( + request: EmptyRequest, + ctx: AsyncRPCContext + ): Future[EmptyReturn] = { + if (dp.executor.asInstanceOf[LoopStartOpExec].checkCondition()){ + val portId = dp.inputGateway + .getChannel(dp.inputManager.currentChannelId) + .getPortId + dp.outputManager.outputIterator.setTupleOutput( + dp.executor.onFinishMultiPort(portId.id) + ) + dp.sendECMToDataChannels(METHOD_END_ITERATION.getBareMethodName, PORT_ALIGNMENT, EndIterationRequest(dp.actorId)) + } + else{ + dp.outputManager.finalizeOutput() + } + EmptyReturn() + } +} From f05840bde928b6a5736386ad97d1b6f22b18e6e0 Mon Sep 17 00:00:00 2001 From: Xinyuan Lin Date: Sun, 22 Jun 2025 05:23:08 -0700 Subject: [PATCH 20/39] fix fmt --- .../promisehandlers/EndChannelHandler.scala | 12 ++++++++-- .../promisehandlers/EndIterationHandler.scala | 6 ++++- .../NextIterationHandler.scala | 21 ++++++++++------- .../uci/ics/amber/operator/LogicalOp.scala | 23 +++++++++++++++---- .../amber/operator/loop/LoopEndOpDesc.scala | 6 ++--- .../amber/operator/loop/LoopStartOpDesc.scala | 6 ++--- .../amber/operator/loop/LoopStartOpExec.scala | 5 ++-- .../amber/operator/sleep/SleepOpDesc.scala | 10 ++++---- .../amber/operator/sleep/SleepOpExec.scala | 2 +- 9 files changed, 61 insertions(+), 30 deletions(-) diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/EndChannelHandler.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/EndChannelHandler.scala index 4cdff3d8325..d1a5dcf4450 100644 --- a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/EndChannelHandler.scala +++ b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/EndChannelHandler.scala @@ -22,7 +22,11 @@ package edu.uci.ics.amber.engine.architecture.worker.promisehandlers import com.twitter.util.Future import edu.uci.ics.amber.core.tuple.FinalizePort import edu.uci.ics.amber.engine.architecture.rpc.controlcommands.EmbeddedControlMessageType.PORT_ALIGNMENT -import edu.uci.ics.amber.engine.architecture.rpc.controlcommands.{AsyncRPCContext, EmptyRequest, EndIterationRequest} +import edu.uci.ics.amber.engine.architecture.rpc.controlcommands.{ + AsyncRPCContext, + EmptyRequest, + EndIterationRequest +} import edu.uci.ics.amber.engine.architecture.rpc.controlreturns.EmptyReturn import edu.uci.ics.amber.engine.architecture.rpc.workerservice.WorkerServiceGrpc.METHOD_END_ITERATION import edu.uci.ics.amber.engine.architecture.worker.DataProcessorRPCHandlerInitializer @@ -64,7 +68,11 @@ trait EndChannelHandler { if (!dp.outputManager.isMissingOutputPort) { dp.executor match { case _: LoopStartOpExec => - dp.sendECMToDataChannels(METHOD_END_ITERATION.getBareMethodName, PORT_ALIGNMENT, EndIterationRequest(dp.actorId)) + dp.sendECMToDataChannels( + METHOD_END_ITERATION.getBareMethodName, + PORT_ALIGNMENT, + EndIterationRequest(dp.actorId) + ) case _ => // assuming all the output ports finalize after all input ports are finalized. dp.outputManager.finalizeOutput() diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/EndIterationHandler.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/EndIterationHandler.scala index faacf364cfe..baf2fba682b 100644 --- a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/EndIterationHandler.scala +++ b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/EndIterationHandler.scala @@ -21,7 +21,11 @@ package edu.uci.ics.amber.engine.architecture.worker.promisehandlers import com.twitter.util.Future import edu.uci.ics.amber.engine.architecture.rpc.controlcommands.EmbeddedControlMessageType.PORT_ALIGNMENT -import edu.uci.ics.amber.engine.architecture.rpc.controlcommands.{AsyncRPCContext, EmptyRequest, EndIterationRequest} +import edu.uci.ics.amber.engine.architecture.rpc.controlcommands.{ + AsyncRPCContext, + EmptyRequest, + EndIterationRequest +} import edu.uci.ics.amber.engine.architecture.rpc.controlreturns.EmptyReturn import edu.uci.ics.amber.engine.architecture.rpc.workerservice.WorkerServiceGrpc.METHOD_END_ITERATION import edu.uci.ics.amber.engine.architecture.worker.DataProcessorRPCHandlerInitializer diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/NextIterationHandler.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/NextIterationHandler.scala index b6f13abde2f..e5524c84df6 100644 --- a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/NextIterationHandler.scala +++ b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/NextIterationHandler.scala @@ -21,7 +21,11 @@ package edu.uci.ics.amber.engine.architecture.worker.promisehandlers import com.twitter.util.Future import edu.uci.ics.amber.engine.architecture.rpc.controlcommands.EmbeddedControlMessageType.PORT_ALIGNMENT -import edu.uci.ics.amber.engine.architecture.rpc.controlcommands.{AsyncRPCContext, EmptyRequest, EndIterationRequest} +import edu.uci.ics.amber.engine.architecture.rpc.controlcommands.{ + AsyncRPCContext, + EmptyRequest, + EndIterationRequest +} import edu.uci.ics.amber.engine.architecture.rpc.controlreturns.EmptyReturn import edu.uci.ics.amber.engine.architecture.rpc.workerservice.WorkerServiceGrpc.METHOD_END_ITERATION import edu.uci.ics.amber.engine.architecture.worker.DataProcessorRPCHandlerInitializer @@ -34,16 +38,17 @@ trait NextIterationHandler { request: EmptyRequest, ctx: AsyncRPCContext ): Future[EmptyReturn] = { - if (dp.executor.asInstanceOf[LoopStartOpExec].checkCondition()){ - val portId = dp.inputGateway - .getChannel(dp.inputManager.currentChannelId) - .getPortId + if (dp.executor.asInstanceOf[LoopStartOpExec].checkCondition()) { + val portId = dp.inputGateway.getChannel(dp.inputManager.currentChannelId).getPortId dp.outputManager.outputIterator.setTupleOutput( dp.executor.onFinishMultiPort(portId.id) ) - dp.sendECMToDataChannels(METHOD_END_ITERATION.getBareMethodName, PORT_ALIGNMENT, EndIterationRequest(dp.actorId)) - } - else{ + dp.sendECMToDataChannels( + METHOD_END_ITERATION.getBareMethodName, + PORT_ALIGNMENT, + EndIterationRequest(dp.actorId) + ) + } else { dp.outputManager.finalizeOutput() } EmptyReturn() diff --git a/core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/LogicalOp.scala b/core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/LogicalOp.scala index 5167492f66e..080a4ce5d25 100644 --- a/core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/LogicalOp.scala +++ b/core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/LogicalOp.scala @@ -24,7 +24,11 @@ import com.fasterxml.jackson.annotation._ import com.kjetland.jackson.jsonSchema.annotations.JsonSchemaTitle import edu.uci.ics.amber.core.executor.OperatorExecutor import edu.uci.ics.amber.core.tuple.Schema -import edu.uci.ics.amber.core.virtualidentity.{ExecutionIdentity, OperatorIdentity, WorkflowIdentity} +import edu.uci.ics.amber.core.virtualidentity.{ + ExecutionIdentity, + OperatorIdentity, + WorkflowIdentity +} import edu.uci.ics.amber.core.workflow.WorkflowContext.{DEFAULT_EXECUTION_ID, DEFAULT_WORKFLOW_ID} import edu.uci.ics.amber.core.workflow.{PhysicalOp, PhysicalPlan, PortIdentity} import edu.uci.ics.amber.operator.aggregate.AggregateOpDesc @@ -35,7 +39,12 @@ import edu.uci.ics.amber.operator.distinct.DistinctOpDesc import edu.uci.ics.amber.operator.dummy.DummyOpDesc import edu.uci.ics.amber.operator.filter.SpecializedFilterOpDesc import edu.uci.ics.amber.operator.hashJoin.HashJoinOpDesc -import edu.uci.ics.amber.operator.huggingFace.{HuggingFaceIrisLogisticRegressionOpDesc, HuggingFaceSentimentAnalysisOpDesc, HuggingFaceSpamSMSDetectionOpDesc, HuggingFaceTextSummarizationOpDesc} +import edu.uci.ics.amber.operator.huggingFace.{ + HuggingFaceIrisLogisticRegressionOpDesc, + HuggingFaceSentimentAnalysisOpDesc, + HuggingFaceSpamSMSDetectionOpDesc, + HuggingFaceTextSummarizationOpDesc +} import edu.uci.ics.amber.operator.ifStatement.IfOpDesc import edu.uci.ics.amber.operator.intersect.IntersectOpDesc import edu.uci.ics.amber.operator.intervalJoin.IntervalJoinOpDesc @@ -43,7 +52,10 @@ import edu.uci.ics.amber.operator.keywordSearch.KeywordSearchOpDesc import edu.uci.ics.amber.operator.limit.LimitOpDesc import edu.uci.ics.amber.operator.loop.{LoopEndOpDesc, LoopStartOpDesc} import edu.uci.ics.amber.operator.machineLearning.Scorer.MachineLearningScorerOpDesc -import edu.uci.ics.amber.operator.machineLearning.sklearnAdvanced.KNNTrainer.{SklearnAdvancedKNNClassifierTrainerOpDesc, SklearnAdvancedKNNRegressorTrainerOpDesc} +import edu.uci.ics.amber.operator.machineLearning.sklearnAdvanced.KNNTrainer.{ + SklearnAdvancedKNNClassifierTrainerOpDesc, + SklearnAdvancedKNNRegressorTrainerOpDesc +} import edu.uci.ics.amber.operator.machineLearning.sklearnAdvanced.SVCTrainer.SklearnAdvancedSVCTrainerOpDesc import edu.uci.ics.amber.operator.machineLearning.sklearnAdvanced.SVRTrainer.SklearnAdvancedSVRTrainerOpDesc import edu.uci.ics.amber.operator.metadata.{OPVersion, OperatorInfo, PropertyNameConstants} @@ -56,7 +68,10 @@ import edu.uci.ics.amber.operator.sleep.SleepOpDesc import edu.uci.ics.amber.operator.sort.SortOpDesc import edu.uci.ics.amber.operator.sortPartitions.SortPartitionsOpDesc import edu.uci.ics.amber.operator.source.apis.reddit.RedditSearchSourceOpDesc -import edu.uci.ics.amber.operator.source.apis.twitter.v2.{TwitterFullArchiveSearchSourceOpDesc, TwitterSearchSourceOpDesc} +import edu.uci.ics.amber.operator.source.apis.twitter.v2.{ + TwitterFullArchiveSearchSourceOpDesc, + TwitterSearchSourceOpDesc +} import edu.uci.ics.amber.operator.source.fetcher.URLFetcherOpDesc import edu.uci.ics.amber.operator.source.scan.FileScanSourceOpDesc import edu.uci.ics.amber.operator.source.scan.arrow.ArrowSourceOpDesc diff --git a/core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/loop/LoopEndOpDesc.scala b/core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/loop/LoopEndOpDesc.scala index b7e743ead3c..e86f27d3ab2 100644 --- a/core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/loop/LoopEndOpDesc.scala +++ b/core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/loop/LoopEndOpDesc.scala @@ -27,9 +27,9 @@ import edu.uci.ics.amber.operator.LogicalOp class LoopEndOpDesc extends LogicalOp { override def getPhysicalOp( - workflowId: WorkflowIdentity, - executionId: ExecutionIdentity - ): PhysicalOp = { + workflowId: WorkflowIdentity, + executionId: ExecutionIdentity + ): PhysicalOp = { PhysicalOp .oneToOnePhysicalOp( workflowId, diff --git a/core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/loop/LoopStartOpDesc.scala b/core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/loop/LoopStartOpDesc.scala index 1a8db3a3eed..79204dc7dcf 100644 --- a/core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/loop/LoopStartOpDesc.scala +++ b/core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/loop/LoopStartOpDesc.scala @@ -35,9 +35,9 @@ class LoopStartOpDesc extends LogicalOp { var iteration: Int = _ override def getPhysicalOp( - workflowId: WorkflowIdentity, - executionId: ExecutionIdentity - ): PhysicalOp = { + workflowId: WorkflowIdentity, + executionId: ExecutionIdentity + ): PhysicalOp = { PhysicalOp .oneToOnePhysicalOp( workflowId, diff --git a/core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/loop/LoopStartOpExec.scala b/core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/loop/LoopStartOpExec.scala index 9128bc185ea..b4b60ee9740 100644 --- a/core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/loop/LoopStartOpExec.scala +++ b/core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/loop/LoopStartOpExec.scala @@ -29,9 +29,8 @@ class LoopStartOpExec(descString: String) extends OperatorExecutor { private val data = new mutable.ArrayBuffer[Tuple] private var currentIteration = 0 - def checkCondition(): Boolean = { - desc.iteration>currentIteration + desc.iteration > currentIteration } override def processTuple(tuple: Tuple, port: Int): Iterator[TupleLike] = { @@ -43,4 +42,4 @@ class LoopStartOpExec(descString: String) extends OperatorExecutor { currentIteration += 1 data.iterator } -} \ No newline at end of file +} diff --git a/core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/sleep/SleepOpDesc.scala b/core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/sleep/SleepOpDesc.scala index f357c9cf084..6edcc502c27 100644 --- a/core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/sleep/SleepOpDesc.scala +++ b/core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/sleep/SleepOpDesc.scala @@ -35,9 +35,9 @@ class SleepOpDesc extends LogicalOp { var time: Int = _ override def getPhysicalOp( - workflowId: WorkflowIdentity, - executionId: ExecutionIdentity - ): PhysicalOp = { + workflowId: WorkflowIdentity, + executionId: ExecutionIdentity + ): PhysicalOp = { PhysicalOp .oneToOnePhysicalOp( workflowId, @@ -61,6 +61,6 @@ class SleepOpDesc extends LogicalOp { "Limit the number of output rows", OperatorGroupConstants.CONTROL_GROUP, inputPorts = List(InputPort()), - outputPorts = List(OutputPort()), + outputPorts = List(OutputPort()) ) -} \ No newline at end of file +} diff --git a/core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/sleep/SleepOpExec.scala b/core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/sleep/SleepOpExec.scala index f6630710508..e7eb1c232d3 100644 --- a/core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/sleep/SleepOpExec.scala +++ b/core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/sleep/SleepOpExec.scala @@ -27,7 +27,7 @@ class SleepOpExec(descString: String) extends OperatorExecutor { private val desc: SleepOpDesc = objectMapper.readValue(descString, classOf[SleepOpDesc]) override def processTuple(tuple: Tuple, port: Int): Iterator[TupleLike] = { - Thread.sleep(1000*desc.time) + Thread.sleep(1000 * desc.time) Iterator(tuple) } } From 1ba81ed77bf060ac70ff55bc3de93995fd2fd653 Mon Sep 17 00:00:00 2001 From: Xinyuan Lin Date: Sun, 22 Jun 2025 05:28:49 -0700 Subject: [PATCH 21/39] fix fmt --- .../uci/ics/amber/operator/loop/LoopEndOpExec.scala | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/loop/LoopEndOpExec.scala b/core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/loop/LoopEndOpExec.scala index 54f399eaccb..ad201f9c30a 100644 --- a/core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/loop/LoopEndOpExec.scala +++ b/core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/loop/LoopEndOpExec.scala @@ -21,15 +21,7 @@ package edu.uci.ics.amber.operator.loop import edu.uci.ics.amber.core.executor.OperatorExecutor import edu.uci.ics.amber.core.tuple.{Tuple, TupleLike} -import scala.collection.mutable class LoopEndOpExec extends OperatorExecutor { - private val data = new mutable.ArrayBuffer[Tuple] - - override def processTuple(tuple: Tuple, port: Int): Iterator[TupleLike] = { - data.append(tuple) - Iterator.empty - } - - override def onFinish(port: Int): Iterator[TupleLike] = data.iterator + override def processTuple(tuple: Tuple, port: Int): Iterator[TupleLike] = Iterator(tuple) } From 7336b523cc2d73152f9a5789187f694b565376a9 Mon Sep 17 00:00:00 2001 From: Xinyuan Lin Date: Sun, 22 Jun 2025 07:37:14 -0700 Subject: [PATCH 22/39] fix fmt --- .../handlers/control/end_iteration_handler.py | 31 ++++++++ .../rpc/async_rpc_handler_initializer.py | 2 + .../python/core/models/internal_marker.py | 8 ++ .../main/python/core/runnables/main_loop.py | 22 ++++-- .../amber/engine/architecture/rpc/__init__.py | 76 +++++++++++++++++++ 5 files changed, 134 insertions(+), 5 deletions(-) create mode 100644 core/amber/src/main/python/core/architecture/handlers/control/end_iteration_handler.py diff --git a/core/amber/src/main/python/core/architecture/handlers/control/end_iteration_handler.py b/core/amber/src/main/python/core/architecture/handlers/control/end_iteration_handler.py new file mode 100644 index 00000000000..3bd55fcd148 --- /dev/null +++ b/core/amber/src/main/python/core/architecture/handlers/control/end_iteration_handler.py @@ -0,0 +1,31 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +from core.architecture.handlers.control.control_handler_base import ControlHandler +from proto.edu.uci.ics.amber.engine.architecture.rpc import ( + EmptyReturn, + EndIterationRequest, +) +from core.models.internal_marker import EndIteration + + +class EndIterationHandler(ControlHandler): + async def end_iteration(self, req: EndIterationRequest) -> EmptyReturn: + self.context.tuple_processing_manager.current_internal_marker = EndIteration( + req + ) + return EmptyReturn() diff --git a/core/amber/src/main/python/core/architecture/rpc/async_rpc_handler_initializer.py b/core/amber/src/main/python/core/architecture/rpc/async_rpc_handler_initializer.py index 5d846c19929..1f93ebdc877 100644 --- a/core/amber/src/main/python/core/architecture/rpc/async_rpc_handler_initializer.py +++ b/core/amber/src/main/python/core/architecture/rpc/async_rpc_handler_initializer.py @@ -27,6 +27,7 @@ ) from core.architecture.handlers.control.start_channel_handler import StartChannelHandler from core.architecture.handlers.control.end_channel_handler import EndChannelHandler +from core.architecture.handlers.control.end_iteration_handler import EndIterationHandler from core.architecture.handlers.control.evaluate_expression_handler import ( EvaluateExpressionHandler, ) @@ -61,6 +62,7 @@ class AsyncRPCHandlerInitializer( StartWorkerHandler, StartChannelHandler, EndChannelHandler, + EndIterationHandler, NoOperationHandler, ): pass diff --git a/core/amber/src/main/python/core/models/internal_marker.py b/core/amber/src/main/python/core/models/internal_marker.py index 6c9c80bafc4..38f50e60444 100644 --- a/core/amber/src/main/python/core/models/internal_marker.py +++ b/core/amber/src/main/python/core/models/internal_marker.py @@ -15,6 +15,9 @@ # specific language governing permissions and limitations # under the License. +from dataclasses import dataclass +from proto.edu.uci.ics.amber.engine.architecture.rpc import EndIterationRequest + class InternalMarker: """ @@ -31,3 +34,8 @@ class StartChannel(InternalMarker): class EndChannel(InternalMarker): pass + + +@dataclass +class EndIteration(InternalMarker): + request: EndIterationRequest diff --git a/core/amber/src/main/python/core/runnables/main_loop.py b/core/amber/src/main/python/core/runnables/main_loop.py index d5ea6522366..bd318420cba 100644 --- a/core/amber/src/main/python/core/runnables/main_loop.py +++ b/core/amber/src/main/python/core/runnables/main_loop.py @@ -31,7 +31,7 @@ InternalQueue, Tuple, ) -from core.models.internal_marker import StartChannel, EndChannel +from core.models.internal_marker import StartChannel, EndChannel, EndIteration from core.models.internal_queue import ( DataElement, ControlElement, @@ -66,6 +66,7 @@ ChannelIdentity, EmbeddedControlMessageIdentity, ) +from core.util import set_one_of class MainLoop(StoppableQueueBlockingRunnable): @@ -296,6 +297,13 @@ def _process_end_channel(self) -> None: ) self.complete() + def _process_end_iteration(self) -> None: + # reset python op here + marker = self.context.tuple_processing_manager.get_internal_marker() + self._send_ecm_to_data_channels( + "EndIteration", EmbeddedControlMessageType.PORT_ALIGNMENT, marker.request + ) + def _process_ecm(self, ecm_element: EmbeddedControlMessageElement): """ Processes a received ECM and handles synchronization, @@ -347,21 +355,25 @@ def _process_ecm(self, ecm_element: EmbeddedControlMessageElement): { StartChannel: self._process_start_channel, EndChannel: self._process_end_channel, + EndIteration: self._process_end_iteration, }[type(self.context.tuple_processing_manager.current_internal_marker)]() def _send_ecm_to_data_channels( - self, method_name: str, alignment: EmbeddedControlMessageType + self, + method: str, + alignment: EmbeddedControlMessageType, + request: ControlRequest = EmptyRequest(), ) -> None: for active_channel_id in self.context.output_manager.get_output_channel_ids(): if not active_channel_id.is_control: ecm = EmbeddedControlMessage( - EmbeddedControlMessageIdentity(method_name), + EmbeddedControlMessageIdentity(method), alignment, [], { active_channel_id.to_worker_id.name: ControlInvocation( - method_name, - ControlRequest(empty_request=EmptyRequest()), + method, + set_one_of(ControlRequest, request), AsyncRpcContext( ActorVirtualIdentity(), ActorVirtualIdentity() ), diff --git a/core/amber/src/main/python/proto/edu/uci/ics/amber/engine/architecture/rpc/__init__.py b/core/amber/src/main/python/proto/edu/uci/ics/amber/engine/architecture/rpc/__init__.py index ae4a12611b1..de36cefcfa5 100644 --- a/core/amber/src/main/python/proto/edu/uci/ics/amber/engine/architecture/rpc/__init__.py +++ b/core/amber/src/main/python/proto/edu/uci/ics/amber/engine/architecture/rpc/__init__.py @@ -139,6 +139,9 @@ class ControlRequest(betterproto.Message): query_statistics_request: "QueryStatisticsRequest" = betterproto.message_field( 58, group="sealed_value" ) + end_iteration_request: "EndIterationRequest" = betterproto.message_field( + 59, group="sealed_value" + ) ping: "Ping" = betterproto.message_field(100, group="sealed_value") """request for testing""" @@ -403,6 +406,11 @@ class QueryStatisticsRequest(betterproto.Message): ) +@dataclass(eq=False, repr=False) +class EndIterationRequest(betterproto.Message): + worker: "___core__.ActorVirtualIdentity" = betterproto.message_field(1) + + @dataclass(eq=False, repr=False) class ControlReturn(betterproto.Message): """The generic return message""" @@ -1235,6 +1243,40 @@ async def end_channel( metadata=metadata, ) + async def end_iteration( + self, + end_iteration_request: "EndIterationRequest", + *, + timeout: Optional[float] = None, + deadline: Optional["Deadline"] = None, + metadata: Optional["MetadataLike"] = None + ) -> "EmptyReturn": + return await self._unary_unary( + "/edu.uci.ics.amber.engine.architecture.rpc.WorkerService/EndIteration", + end_iteration_request, + EmptyReturn, + timeout=timeout, + deadline=deadline, + metadata=metadata, + ) + + async def next_iteration( + self, + empty_request: "EmptyRequest", + *, + timeout: Optional[float] = None, + deadline: Optional["Deadline"] = None, + metadata: Optional["MetadataLike"] = None + ) -> "EmptyReturn": + return await self._unary_unary( + "/edu.uci.ics.amber.engine.architecture.rpc.WorkerService/NextIteration", + empty_request, + EmptyReturn, + timeout=timeout, + deadline=deadline, + metadata=metadata, + ) + async def debug_command( self, debug_command_request: "DebugCommandRequest", @@ -1809,6 +1851,14 @@ async def start_channel(self, empty_request: "EmptyRequest") -> "EmptyReturn": async def end_channel(self, empty_request: "EmptyRequest") -> "EmptyReturn": raise grpclib.GRPCError(grpclib.const.Status.UNIMPLEMENTED) + async def end_iteration( + self, end_iteration_request: "EndIterationRequest" + ) -> "EmptyReturn": + raise grpclib.GRPCError(grpclib.const.Status.UNIMPLEMENTED) + + async def next_iteration(self, empty_request: "EmptyRequest") -> "EmptyReturn": + raise grpclib.GRPCError(grpclib.const.Status.UNIMPLEMENTED) + async def debug_command( self, debug_command_request: "DebugCommandRequest" ) -> "EmptyReturn": @@ -1935,6 +1985,20 @@ async def __rpc_end_channel( response = await self.end_channel(request) await stream.send_message(response) + async def __rpc_end_iteration( + self, stream: "grpclib.server.Stream[EndIterationRequest, EmptyReturn]" + ) -> None: + request = await stream.recv_message() + response = await self.end_iteration(request) + await stream.send_message(response) + + async def __rpc_next_iteration( + self, stream: "grpclib.server.Stream[EmptyRequest, EmptyReturn]" + ) -> None: + request = await stream.recv_message() + response = await self.next_iteration(request) + await stream.send_message(response) + async def __rpc_debug_command( self, stream: "grpclib.server.Stream[DebugCommandRequest, EmptyReturn]" ) -> None: @@ -2055,6 +2119,18 @@ def __mapping__(self) -> Dict[str, grpclib.const.Handler]: EmptyRequest, EmptyReturn, ), + "/edu.uci.ics.amber.engine.architecture.rpc.WorkerService/EndIteration": grpclib.const.Handler( + self.__rpc_end_iteration, + grpclib.const.Cardinality.UNARY_UNARY, + EndIterationRequest, + EmptyReturn, + ), + "/edu.uci.ics.amber.engine.architecture.rpc.WorkerService/NextIteration": grpclib.const.Handler( + self.__rpc_next_iteration, + grpclib.const.Cardinality.UNARY_UNARY, + EmptyRequest, + EmptyReturn, + ), "/edu.uci.ics.amber.engine.architecture.rpc.WorkerService/DebugCommand": grpclib.const.Handler( self.__rpc_debug_command, grpclib.const.Cardinality.UNARY_UNARY, From bca669f08279513780250d5c0e4d04bab2f3e72e Mon Sep 17 00:00:00 2001 From: Xinyuan Lin Date: Sun, 22 Jun 2025 20:46:41 -0700 Subject: [PATCH 23/39] refactor --- ...ut_port_materialization_reader_runnable.py | 1 + .../architecture/worker/DataProcessor.scala | 76 ++++++++++--------- .../promisehandlers/EndChannelHandler.scala | 16 +--- .../promisehandlers/EndIterationHandler.scala | 13 +--- .../NextIterationHandler.scala | 7 +- .../promisehandlers/StartChannelHandler.scala | 12 +-- .../uci/ics/amber/core/tuple/TupleLike.scala | 2 + 7 files changed, 53 insertions(+), 74 deletions(-) diff --git a/core/amber/src/main/python/core/storage/runnables/input_port_materialization_reader_runnable.py b/core/amber/src/main/python/core/storage/runnables/input_port_materialization_reader_runnable.py index 0e67462694e..760225a1681 100644 --- a/core/amber/src/main/python/core/storage/runnables/input_port_materialization_reader_runnable.py +++ b/core/amber/src/main/python/core/storage/runnables/input_port_materialization_reader_runnable.py @@ -150,6 +150,7 @@ def run(self) -> None: for data_frame in self.tuple_to_batch_with_filter(tup): self.emit_payload(data_frame) self.emit_ecm("EndChannel", EmbeddedControlMessageType.PORT_ALIGNMENT) + #self.emit_ecm("EndIteration", EmbeddedControlMessageType.PORT_ALIGNMENT) self._finished = True except Exception as err: logger.exception(err) diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/DataProcessor.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/DataProcessor.scala index 467b0641b0e..e21fd2ec2ef 100644 --- a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/DataProcessor.scala +++ b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/DataProcessor.scala @@ -22,49 +22,23 @@ package edu.uci.ics.amber.engine.architecture.worker import com.softwaremill.macwire.wire import edu.uci.ics.amber.core.executor.OperatorExecutor import edu.uci.ics.amber.core.state.State -import edu.uci.ics.amber.core.tuple.{ - FinalizeExecutor, - FinalizePort, - SchemaEnforceable, - Tuple, - TupleLike -} +import edu.uci.ics.amber.core.tuple.{FinalizeExecutor, FinalizeIteration, FinalizePort, SchemaEnforceable, Tuple, TupleLike} import edu.uci.ics.amber.engine.architecture.common.AmberProcessor import edu.uci.ics.amber.engine.architecture.logreplay.ReplayLogManager -import edu.uci.ics.amber.engine.architecture.messaginglayer.{ - InputManager, - OutputManager, - WorkerTimerService -} -import edu.uci.ics.amber.engine.architecture.rpc.controlcommands.EmbeddedControlMessageType.{ - NO_ALIGNMENT, - PORT_ALIGNMENT -} +import edu.uci.ics.amber.engine.architecture.messaginglayer.{InputManager, OutputManager, WorkerTimerService} +import edu.uci.ics.amber.engine.architecture.rpc.controlcommands.EmbeddedControlMessageType.{NO_ALIGNMENT, PORT_ALIGNMENT} import edu.uci.ics.amber.engine.architecture.rpc.controlcommands._ -import edu.uci.ics.amber.engine.architecture.worker.WorkflowWorker.{ - DPInputQueueElement, - MainThreadDelegateMessage -} +import edu.uci.ics.amber.engine.architecture.worker.WorkflowWorker.{DPInputQueueElement, MainThreadDelegateMessage} import edu.uci.ics.amber.engine.architecture.worker.managers.SerializationManager -import edu.uci.ics.amber.engine.architecture.worker.statistics.WorkerState.{ - COMPLETED, - READY, - RUNNING -} +import edu.uci.ics.amber.engine.architecture.worker.statistics.WorkerState.{COMPLETED, READY, RUNNING} import edu.uci.ics.amber.engine.architecture.worker.statistics.WorkerStatistics import edu.uci.ics.amber.engine.common.ambermessage._ import edu.uci.ics.amber.engine.common.statetransition.WorkerStateManager import edu.uci.ics.amber.engine.common.virtualidentity.util.CONTROLLER import edu.uci.ics.amber.error.ErrorUtils.{mkConsoleMessage, safely} -import edu.uci.ics.amber.core.virtualidentity.{ - ActorVirtualIdentity, - ChannelIdentity, - EmbeddedControlMessageIdentity -} +import edu.uci.ics.amber.core.virtualidentity.{ActorVirtualIdentity, ChannelIdentity, EmbeddedControlMessageIdentity} import edu.uci.ics.amber.core.workflow.PortIdentity -import edu.uci.ics.amber.engine.architecture.rpc.controlreturns.EmptyReturn -import edu.uci.ics.amber.engine.architecture.rpc.workerservice.WorkerServiceGrpc.METHOD_END_CHANNEL -import io.grpc.MethodDescriptor +import edu.uci.ics.amber.engine.architecture.rpc.workerservice.WorkerServiceGrpc.{METHOD_END_CHANNEL, METHOD_END_ITERATION} import java.util.concurrent.LinkedBlockingQueue @@ -139,6 +113,7 @@ class DataProcessor( } } + /** transfer one tuple from iterator to downstream. * this function is only called by the DP thread */ @@ -183,6 +158,9 @@ class DataProcessor( PortCompletedRequest(portId, input), asyncRPCClient.mkContext(CONTROLLER) ) + case FinalizeIteration(worker: ActorVirtualIdentity) => + sendECMToDataChannels(METHOD_END_ITERATION.getBareMethodName, PORT_ALIGNMENT, EndIterationRequest(worker)) + executor.reset() case schemaEnforceable: SchemaEnforceable => val portIdentity = outputPortOpt.getOrElse(outputManager.getSingleOutputPortIdentity) val tuple = schemaEnforceable.enforceSchema(outputManager.getPort(portIdentity).schema) @@ -268,6 +246,36 @@ class DataProcessor( } } + def processOnStart(): Unit = { + val portId = inputGateway.getChannel(inputManager.currentChannelId).getPortId + try { + val outputState = executor.produceStateOnStart(portId.id) + if (outputState.isDefined) { + outputManager.emitState(outputState.get) + } + } catch safely { + case e => + handleExecutorException(e) + } + } + + def processOnFinish(): Unit = { + val portId = inputGateway.getChannel(inputManager.currentChannelId).getPortId + try { + val outputState = executor.produceStateOnFinish(portId.id) + if (outputState.isDefined) { + outputManager.emitState(outputState.get) + } + outputManager.outputIterator.setTupleOutput( + executor.onFinishMultiPort(portId.id) + ) + } catch safely { + case e => + // forward input tuple to the user and pause DP thread + handleExecutorException(e) + } + } + def sendECMToDataChannels( method: String, alignment: EmbeddedControlMessageType, @@ -295,7 +303,7 @@ class DataProcessor( } } - def handleExecutorException(e: Throwable): Unit = { + private[this] def handleExecutorException(e: Throwable): Unit = { asyncRPCClient.controllerInterface.consoleMessageTriggered( ConsoleMessageTriggeredRequest(mkConsoleMessage(actorId, e)), asyncRPCClient.mkContext(CONTROLLER) diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/EndChannelHandler.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/EndChannelHandler.scala index d1a5dcf4450..9b012737480 100644 --- a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/EndChannelHandler.scala +++ b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/EndChannelHandler.scala @@ -30,7 +30,6 @@ import edu.uci.ics.amber.engine.architecture.rpc.controlcommands.{ import edu.uci.ics.amber.engine.architecture.rpc.controlreturns.EmptyReturn import edu.uci.ics.amber.engine.architecture.rpc.workerservice.WorkerServiceGrpc.METHOD_END_ITERATION import edu.uci.ics.amber.engine.architecture.worker.DataProcessorRPCHandlerInitializer -import edu.uci.ics.amber.error.ErrorUtils.safely import edu.uci.ics.amber.operator.loop.LoopStartOpExec trait EndChannelHandler { @@ -44,20 +43,7 @@ trait EndChannelHandler { val portId = dp.inputGateway.getChannel(channelId).getPortId dp.inputManager.getPort(portId).completed = true dp.inputManager.initBatch(channelId, Array.empty) - try { - val outputState = dp.executor.produceStateOnFinish(portId.id) - if (outputState.isDefined) { - dp.outputManager.emitState(outputState.get) - } - dp.outputManager.outputIterator.setTupleOutput( - dp.executor.onFinishMultiPort(portId.id) - ) - } catch safely { - case e => - // forward input tuple to the user and pause DP thread - dp.handleExecutorException(e) - } - + dp.processOnFinish() dp.outputManager.outputIterator.appendSpecialTupleToEnd( FinalizePort(portId, input = true) ) diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/EndIterationHandler.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/EndIterationHandler.scala index baf2fba682b..6b7d16b86f2 100644 --- a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/EndIterationHandler.scala +++ b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/EndIterationHandler.scala @@ -20,14 +20,9 @@ package edu.uci.ics.amber.engine.architecture.worker.promisehandlers import com.twitter.util.Future -import edu.uci.ics.amber.engine.architecture.rpc.controlcommands.EmbeddedControlMessageType.PORT_ALIGNMENT -import edu.uci.ics.amber.engine.architecture.rpc.controlcommands.{ - AsyncRPCContext, - EmptyRequest, - EndIterationRequest -} +import edu.uci.ics.amber.core.tuple.FinalizeIteration +import edu.uci.ics.amber.engine.architecture.rpc.controlcommands.{AsyncRPCContext, EmptyRequest, EndIterationRequest} import edu.uci.ics.amber.engine.architecture.rpc.controlreturns.EmptyReturn -import edu.uci.ics.amber.engine.architecture.rpc.workerservice.WorkerServiceGrpc.METHOD_END_ITERATION import edu.uci.ics.amber.engine.architecture.worker.DataProcessorRPCHandlerInitializer import edu.uci.ics.amber.operator.loop.LoopEndOpExec @@ -42,8 +37,8 @@ trait EndIterationHandler { case _: LoopEndOpExec => workerInterface.nextIteration(EmptyRequest(), mkContext(request.worker)) case _ => - dp.executor.reset() - dp.sendECMToDataChannels(METHOD_END_ITERATION.getBareMethodName, PORT_ALIGNMENT, request) + dp.processOnFinish() + dp.outputManager.outputIterator.appendSpecialTupleToEnd(FinalizeIteration(request.worker)) } EmptyReturn() } diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/NextIterationHandler.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/NextIterationHandler.scala index e5524c84df6..fbfd2e30247 100644 --- a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/NextIterationHandler.scala +++ b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/NextIterationHandler.scala @@ -29,7 +29,7 @@ import edu.uci.ics.amber.engine.architecture.rpc.controlcommands.{ import edu.uci.ics.amber.engine.architecture.rpc.controlreturns.EmptyReturn import edu.uci.ics.amber.engine.architecture.rpc.workerservice.WorkerServiceGrpc.METHOD_END_ITERATION import edu.uci.ics.amber.engine.architecture.worker.DataProcessorRPCHandlerInitializer -import edu.uci.ics.amber.operator.loop.{LoopEndOpExec, LoopStartOpExec} +import edu.uci.ics.amber.operator.loop.LoopStartOpExec trait NextIterationHandler { this: DataProcessorRPCHandlerInitializer => @@ -39,10 +39,7 @@ trait NextIterationHandler { ctx: AsyncRPCContext ): Future[EmptyReturn] = { if (dp.executor.asInstanceOf[LoopStartOpExec].checkCondition()) { - val portId = dp.inputGateway.getChannel(dp.inputManager.currentChannelId).getPortId - dp.outputManager.outputIterator.setTupleOutput( - dp.executor.onFinishMultiPort(portId.id) - ) + dp.processOnFinish() dp.sendECMToDataChannels( METHOD_END_ITERATION.getBareMethodName, PORT_ALIGNMENT, diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/StartChannelHandler.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/StartChannelHandler.scala index fe38d7aff85..227c029290a 100644 --- a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/StartChannelHandler.scala +++ b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/StartChannelHandler.scala @@ -25,7 +25,6 @@ import edu.uci.ics.amber.engine.architecture.rpc.controlcommands.{AsyncRPCContex import edu.uci.ics.amber.engine.architecture.rpc.controlreturns.EmptyReturn import edu.uci.ics.amber.engine.architecture.rpc.workerservice.WorkerServiceGrpc.METHOD_START_CHANNEL import edu.uci.ics.amber.engine.architecture.worker.DataProcessorRPCHandlerInitializer -import edu.uci.ics.amber.error.ErrorUtils.safely trait StartChannelHandler { this: DataProcessorRPCHandlerInitializer => @@ -34,17 +33,8 @@ trait StartChannelHandler { request: EmptyRequest, ctx: AsyncRPCContext ): Future[EmptyReturn] = { - val portId = dp.inputGateway.getChannel(dp.inputManager.currentChannelId).getPortId dp.sendECMToDataChannels(METHOD_START_CHANNEL.getBareMethodName, NO_ALIGNMENT) - try { - val outputState = dp.executor.produceStateOnStart(portId.id) - if (outputState.isDefined) { - dp.outputManager.emitState(outputState.get) - } - } catch safely { - case e => - dp.handleExecutorException(e) - } + dp.processOnStart() EmptyReturn() } } diff --git a/core/workflow-core/src/main/scala/edu/uci/ics/amber/core/tuple/TupleLike.scala b/core/workflow-core/src/main/scala/edu/uci/ics/amber/core/tuple/TupleLike.scala index 0cf030f964a..c0053fb1948 100644 --- a/core/workflow-core/src/main/scala/edu/uci/ics/amber/core/tuple/TupleLike.scala +++ b/core/workflow-core/src/main/scala/edu/uci/ics/amber/core/tuple/TupleLike.scala @@ -19,6 +19,7 @@ package edu.uci.ics.amber.core.tuple +import edu.uci.ics.amber.core.virtualidentity.ActorVirtualIdentity import edu.uci.ics.amber.core.workflow.PortIdentity import scala.jdk.CollectionConverters.CollectionHasAsScala @@ -41,6 +42,7 @@ trait InternalMarker extends TupleLike { final case class FinalizePort(portId: PortIdentity, input: Boolean) extends InternalMarker final case class FinalizeExecutor() extends InternalMarker +final case class FinalizeIteration(worker: ActorVirtualIdentity) extends InternalMarker trait SeqTupleLike extends TupleLike with SchemaEnforceable { override def inMemSize: Long = ??? From 082b1ff8cf84e45775586bebb2f2c282efc35beb Mon Sep 17 00:00:00 2001 From: Xinyuan Lin Date: Sun, 22 Jun 2025 21:19:37 -0700 Subject: [PATCH 24/39] refactor --- .../handlers/control/end_iteration_handler.py | 3 ++- .../amber/src/main/python/core/models/internal_marker.py | 4 ++-- .../src/main/python/core/runnables/data_processor.py | 4 ++-- core/amber/src/main/python/core/runnables/main_loop.py | 9 +++++++-- 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/core/amber/src/main/python/core/architecture/handlers/control/end_iteration_handler.py b/core/amber/src/main/python/core/architecture/handlers/control/end_iteration_handler.py index 3bd55fcd148..df9e7a2a77d 100644 --- a/core/amber/src/main/python/core/architecture/handlers/control/end_iteration_handler.py +++ b/core/amber/src/main/python/core/architecture/handlers/control/end_iteration_handler.py @@ -25,7 +25,8 @@ class EndIterationHandler(ControlHandler): async def end_iteration(self, req: EndIterationRequest) -> EmptyReturn: + print("ergerhgerherh") self.context.tuple_processing_manager.current_internal_marker = EndIteration( - req + req.worker ) return EmptyReturn() diff --git a/core/amber/src/main/python/core/models/internal_marker.py b/core/amber/src/main/python/core/models/internal_marker.py index 38f50e60444..1a6302f35a3 100644 --- a/core/amber/src/main/python/core/models/internal_marker.py +++ b/core/amber/src/main/python/core/models/internal_marker.py @@ -16,7 +16,7 @@ # under the License. from dataclasses import dataclass -from proto.edu.uci.ics.amber.engine.architecture.rpc import EndIterationRequest +from proto.edu.uci.ics.amber.core import ActorVirtualIdentity class InternalMarker: @@ -38,4 +38,4 @@ class EndChannel(InternalMarker): @dataclass class EndIteration(InternalMarker): - request: EndIterationRequest + worker: ActorVirtualIdentity diff --git a/core/amber/src/main/python/core/runnables/data_processor.py b/core/amber/src/main/python/core/runnables/data_processor.py index 7130c7676b8..0bf3a6e0907 100644 --- a/core/amber/src/main/python/core/runnables/data_processor.py +++ b/core/amber/src/main/python/core/runnables/data_processor.py @@ -24,7 +24,7 @@ from typing import Iterator, Optional from core.architecture.managers import Context from core.models import ExceptionInfo, State, TupleLike, InternalMarker -from core.models.internal_marker import StartChannel, EndChannel +from core.models.internal_marker import StartChannel, EndChannel, EndIteration from core.models.table import all_output_to_tuple from core.util import Stoppable from core.util.console_message.replace_print import replace_print @@ -74,7 +74,7 @@ def process_internal_marker(self, internal_marker: InternalMarker) -> None: ): if isinstance(internal_marker, StartChannel): self._set_output_state(executor.produce_state_on_start(port_id)) - elif isinstance(internal_marker, EndChannel): + elif isinstance(internal_marker, EndChannel) or isinstance(internal_marker, EndIteration): self._set_output_state(executor.produce_state_on_finish(port_id)) self._switch_context() self._set_output_tuple(executor.on_finish(port_id)) diff --git a/core/amber/src/main/python/core/runnables/main_loop.py b/core/amber/src/main/python/core/runnables/main_loop.py index bd318420cba..3c0cbb867da 100644 --- a/core/amber/src/main/python/core/runnables/main_loop.py +++ b/core/amber/src/main/python/core/runnables/main_loop.py @@ -55,6 +55,7 @@ EmbeddedControlMessage, AsyncRpcContext, ControlRequest, + EndIterationRequest, ) from proto.edu.uci.ics.amber.engine.architecture.worker import ( WorkerState, @@ -298,10 +299,14 @@ def _process_end_channel(self) -> None: self.complete() def _process_end_iteration(self) -> None: + worker_id = self.context.tuple_processing_manager.current_internal_marker.worker + print("ergergerger") + self.process_input_state() + self.process_input_tuple() # reset python op here - marker = self.context.tuple_processing_manager.get_internal_marker() + self._send_ecm_to_data_channels( - "EndIteration", EmbeddedControlMessageType.PORT_ALIGNMENT, marker.request + "EndIteration", EmbeddedControlMessageType.PORT_ALIGNMENT, EndIterationRequest(worker_id) ) def _process_ecm(self, ecm_element: EmbeddedControlMessageElement): From bfb59460021107dc79410d23c1e82625e7b665ba Mon Sep 17 00:00:00 2001 From: Xinyuan Lin Date: Tue, 24 Jun 2025 22:53:25 -0700 Subject: [PATCH 25/39] fix fmt --- core/amber/src/main/python/core/runnables/main_loop.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/amber/src/main/python/core/runnables/main_loop.py b/core/amber/src/main/python/core/runnables/main_loop.py index 21804a20233..96637c24cd5 100644 --- a/core/amber/src/main/python/core/runnables/main_loop.py +++ b/core/amber/src/main/python/core/runnables/main_loop.py @@ -290,11 +290,11 @@ def _process_end_iteration(self) -> None: worker_id = self.context.tuple_processing_manager.current_internal_marker.worker self.process_input_state() self.process_input_tuple() - # reset python op here - self._send_ecm_to_data_channels( "EndIteration", EmbeddedControlMessageType.PORT_ALIGNMENT, EndIterationRequest(worker_id) ) + self.context.executor_manager.executor.close() + self.context.executor_manager.executor.open() def _process_ecm(self, ecm_element: ECMElement): """ From 714ca9d77c25ce7877a1f1be3e25e421564e1669 Mon Sep 17 00:00:00 2001 From: Xinyuan Lin Date: Tue, 24 Jun 2025 22:55:29 -0700 Subject: [PATCH 26/39] fix fmt --- .../python/core/runnables/data_processor.py | 4 +- .../main/python/core/runnables/main_loop.py | 4 +- ...ut_port_materialization_reader_runnable.py | 1 - .../architecture/worker/DataProcessor.scala | 49 +++++++++++++++---- .../promisehandlers/EndIterationHandler.scala | 6 ++- 5 files changed, 51 insertions(+), 13 deletions(-) diff --git a/core/amber/src/main/python/core/runnables/data_processor.py b/core/amber/src/main/python/core/runnables/data_processor.py index 0bf3a6e0907..e06688ea77b 100644 --- a/core/amber/src/main/python/core/runnables/data_processor.py +++ b/core/amber/src/main/python/core/runnables/data_processor.py @@ -74,7 +74,9 @@ def process_internal_marker(self, internal_marker: InternalMarker) -> None: ): if isinstance(internal_marker, StartChannel): self._set_output_state(executor.produce_state_on_start(port_id)) - elif isinstance(internal_marker, EndChannel) or isinstance(internal_marker, EndIteration): + elif isinstance(internal_marker, EndChannel) or isinstance( + internal_marker, EndIteration + ): self._set_output_state(executor.produce_state_on_finish(port_id)) self._switch_context() self._set_output_tuple(executor.on_finish(port_id)) diff --git a/core/amber/src/main/python/core/runnables/main_loop.py b/core/amber/src/main/python/core/runnables/main_loop.py index 96637c24cd5..f9b921820a2 100644 --- a/core/amber/src/main/python/core/runnables/main_loop.py +++ b/core/amber/src/main/python/core/runnables/main_loop.py @@ -291,7 +291,9 @@ def _process_end_iteration(self) -> None: self.process_input_state() self.process_input_tuple() self._send_ecm_to_data_channels( - "EndIteration", EmbeddedControlMessageType.PORT_ALIGNMENT, EndIterationRequest(worker_id) + "EndIteration", + EmbeddedControlMessageType.PORT_ALIGNMENT, + EndIterationRequest(worker_id), ) self.context.executor_manager.executor.close() self.context.executor_manager.executor.open() diff --git a/core/amber/src/main/python/core/storage/runnables/input_port_materialization_reader_runnable.py b/core/amber/src/main/python/core/storage/runnables/input_port_materialization_reader_runnable.py index 04ac9de8f78..2fa25fdf4ac 100644 --- a/core/amber/src/main/python/core/storage/runnables/input_port_materialization_reader_runnable.py +++ b/core/amber/src/main/python/core/storage/runnables/input_port_materialization_reader_runnable.py @@ -150,7 +150,6 @@ def run(self) -> None: for data_frame in self.tuple_to_batch_with_filter(tup): self.emit_payload(data_frame) self.emit_ecm("EndChannel", EmbeddedControlMessageType.PORT_ALIGNMENT) - #self.emit_ecm("EndIteration", EmbeddedControlMessageType.PORT_ALIGNMENT) self._finished = True except Exception as err: logger.exception(err) diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/DataProcessor.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/DataProcessor.scala index e21fd2ec2ef..f0972ba3f97 100644 --- a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/DataProcessor.scala +++ b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/DataProcessor.scala @@ -22,23 +22,51 @@ package edu.uci.ics.amber.engine.architecture.worker import com.softwaremill.macwire.wire import edu.uci.ics.amber.core.executor.OperatorExecutor import edu.uci.ics.amber.core.state.State -import edu.uci.ics.amber.core.tuple.{FinalizeExecutor, FinalizeIteration, FinalizePort, SchemaEnforceable, Tuple, TupleLike} +import edu.uci.ics.amber.core.tuple.{ + FinalizeExecutor, + FinalizeIteration, + FinalizePort, + SchemaEnforceable, + Tuple, + TupleLike +} import edu.uci.ics.amber.engine.architecture.common.AmberProcessor import edu.uci.ics.amber.engine.architecture.logreplay.ReplayLogManager -import edu.uci.ics.amber.engine.architecture.messaginglayer.{InputManager, OutputManager, WorkerTimerService} -import edu.uci.ics.amber.engine.architecture.rpc.controlcommands.EmbeddedControlMessageType.{NO_ALIGNMENT, PORT_ALIGNMENT} +import edu.uci.ics.amber.engine.architecture.messaginglayer.{ + InputManager, + OutputManager, + WorkerTimerService +} +import edu.uci.ics.amber.engine.architecture.rpc.controlcommands.EmbeddedControlMessageType.{ + NO_ALIGNMENT, + PORT_ALIGNMENT +} import edu.uci.ics.amber.engine.architecture.rpc.controlcommands._ -import edu.uci.ics.amber.engine.architecture.worker.WorkflowWorker.{DPInputQueueElement, MainThreadDelegateMessage} +import edu.uci.ics.amber.engine.architecture.worker.WorkflowWorker.{ + DPInputQueueElement, + MainThreadDelegateMessage +} import edu.uci.ics.amber.engine.architecture.worker.managers.SerializationManager -import edu.uci.ics.amber.engine.architecture.worker.statistics.WorkerState.{COMPLETED, READY, RUNNING} +import edu.uci.ics.amber.engine.architecture.worker.statistics.WorkerState.{ + COMPLETED, + READY, + RUNNING +} import edu.uci.ics.amber.engine.architecture.worker.statistics.WorkerStatistics import edu.uci.ics.amber.engine.common.ambermessage._ import edu.uci.ics.amber.engine.common.statetransition.WorkerStateManager import edu.uci.ics.amber.engine.common.virtualidentity.util.CONTROLLER import edu.uci.ics.amber.error.ErrorUtils.{mkConsoleMessage, safely} -import edu.uci.ics.amber.core.virtualidentity.{ActorVirtualIdentity, ChannelIdentity, EmbeddedControlMessageIdentity} +import edu.uci.ics.amber.core.virtualidentity.{ + ActorVirtualIdentity, + ChannelIdentity, + EmbeddedControlMessageIdentity +} import edu.uci.ics.amber.core.workflow.PortIdentity -import edu.uci.ics.amber.engine.architecture.rpc.workerservice.WorkerServiceGrpc.{METHOD_END_CHANNEL, METHOD_END_ITERATION} +import edu.uci.ics.amber.engine.architecture.rpc.workerservice.WorkerServiceGrpc.{ + METHOD_END_CHANNEL, + METHOD_END_ITERATION +} import java.util.concurrent.LinkedBlockingQueue @@ -113,7 +141,6 @@ class DataProcessor( } } - /** transfer one tuple from iterator to downstream. * this function is only called by the DP thread */ @@ -159,7 +186,11 @@ class DataProcessor( asyncRPCClient.mkContext(CONTROLLER) ) case FinalizeIteration(worker: ActorVirtualIdentity) => - sendECMToDataChannels(METHOD_END_ITERATION.getBareMethodName, PORT_ALIGNMENT, EndIterationRequest(worker)) + sendECMToDataChannels( + METHOD_END_ITERATION.getBareMethodName, + PORT_ALIGNMENT, + EndIterationRequest(worker) + ) executor.reset() case schemaEnforceable: SchemaEnforceable => val portIdentity = outputPortOpt.getOrElse(outputManager.getSingleOutputPortIdentity) diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/EndIterationHandler.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/EndIterationHandler.scala index 6b7d16b86f2..dab0cfa747d 100644 --- a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/EndIterationHandler.scala +++ b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/EndIterationHandler.scala @@ -21,7 +21,11 @@ package edu.uci.ics.amber.engine.architecture.worker.promisehandlers import com.twitter.util.Future import edu.uci.ics.amber.core.tuple.FinalizeIteration -import edu.uci.ics.amber.engine.architecture.rpc.controlcommands.{AsyncRPCContext, EmptyRequest, EndIterationRequest} +import edu.uci.ics.amber.engine.architecture.rpc.controlcommands.{ + AsyncRPCContext, + EmptyRequest, + EndIterationRequest +} import edu.uci.ics.amber.engine.architecture.rpc.controlreturns.EmptyReturn import edu.uci.ics.amber.engine.architecture.worker.DataProcessorRPCHandlerInitializer import edu.uci.ics.amber.operator.loop.LoopEndOpExec From e71981726974459d5a666c97c62c3f6587f59731 Mon Sep 17 00:00:00 2001 From: Xinyuan Lin Date: Thu, 26 Jun 2025 19:40:35 -0700 Subject: [PATCH 27/39] fix fmt --- core/gui/src/assets/operator_images/sleep.png | Bin 0 -> 59428 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 core/gui/src/assets/operator_images/sleep.png diff --git a/core/gui/src/assets/operator_images/sleep.png b/core/gui/src/assets/operator_images/sleep.png new file mode 100644 index 0000000000000000000000000000000000000000..4a21e236ef65f3b711948018a578de9c2cba1f83 GIT binary patch literal 59428 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 H^uzxE#w|6S literal 0 HcmV?d00001 From 01aaecd1d6de14343f2de8879fe82d29f29c5889 Mon Sep 17 00:00:00 2001 From: Xinyuan Lin Date: Wed, 2 Jul 2025 20:43:40 -0700 Subject: [PATCH 28/39] update --- .../operator_images/{sleep.png => Sleep.png} | Bin .../SklearnLRTrainingClassifierOpDesc.scala | 124 ++++++++++++++++++ .../amber/operator/sleep/SleepOpDesc.scala | 4 +- 3 files changed, 126 insertions(+), 2 deletions(-) rename core/gui/src/assets/operator_images/{sleep.png => Sleep.png} (100%) create mode 100644 core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/sklearn/training/SklearnLRTrainingClassifierOpDesc.scala diff --git a/core/gui/src/assets/operator_images/sleep.png b/core/gui/src/assets/operator_images/Sleep.png similarity index 100% rename from core/gui/src/assets/operator_images/sleep.png rename to core/gui/src/assets/operator_images/Sleep.png diff --git a/core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/sklearn/training/SklearnLRTrainingClassifierOpDesc.scala b/core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/sklearn/training/SklearnLRTrainingClassifierOpDesc.scala new file mode 100644 index 00000000000..5ff1c029c39 --- /dev/null +++ b/core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/sklearn/training/SklearnLRTrainingClassifierOpDesc.scala @@ -0,0 +1,124 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package edu.uci.ics.amber.operator.sklearn.training + +import com.fasterxml.jackson.annotation.{JsonIgnore, JsonProperty, JsonPropertyDescription} +import com.kjetland.jackson.jsonSchema.annotations.{ + JsonSchemaInject, + JsonSchemaInt, + JsonSchemaString, + JsonSchemaTitle +} +import edu.uci.ics.amber.core.tuple.{AttributeType, Schema} +import edu.uci.ics.amber.core.workflow.{InputPort, OutputPort, PortIdentity} +import edu.uci.ics.amber.operator.PythonOperatorDescriptor +import edu.uci.ics.amber.operator.metadata.annotations.{ + AutofillAttributeName, + CommonOpDescAnnotation, + HideAnnotation +} +import edu.uci.ics.amber.operator.metadata.{OperatorGroupConstants, OperatorInfo} + +class SklearnLRTrainingClassifierOpDesc extends PythonOperatorDescriptor { + + @JsonSchemaTitle("Target Attribute") + @JsonPropertyDescription("Attribute in your dataset corresponding to target.") + @JsonProperty(required = true) + @AutofillAttributeName + var target: String = _ + + @JsonSchemaTitle("Count Vectorizer") + @JsonPropertyDescription("Convert a collection of text documents to a matrix of token counts.") + @JsonProperty(defaultValue = "false") + var countVectorizer: Boolean = false + + @JsonSchemaTitle("Text Attribute") + @JsonPropertyDescription("Attribute in your dataset with text to vectorize.") + @JsonSchemaInject( + strings = Array( + new JsonSchemaString( + path = CommonOpDescAnnotation.autofill, + value = CommonOpDescAnnotation.attributeName + ), + new JsonSchemaString(path = HideAnnotation.hideTarget, value = "countVectorizer"), + new JsonSchemaString(path = HideAnnotation.hideType, value = HideAnnotation.Type.equals), + new JsonSchemaString(path = HideAnnotation.hideExpectedValue, value = "false") + ), + ints = Array( + new JsonSchemaInt(path = CommonOpDescAnnotation.autofillAttributeOnPort, value = 0) + ) + ) + var text: String = _ + + @JsonSchemaTitle("Tfidf Transformer") + @JsonPropertyDescription("Transform a count matrix to a normalized tf or tf-idf representation.") + @JsonProperty(defaultValue = "false") + @JsonSchemaInject( + strings = Array( + new JsonSchemaString(path = HideAnnotation.hideTarget, value = "countVectorizer"), + new JsonSchemaString(path = HideAnnotation.hideType, value = HideAnnotation.Type.equals), + new JsonSchemaString(path = HideAnnotation.hideExpectedValue, value = "false") + ) + ) + val tfidfTransformer: Boolean = false + + @JsonIgnore + def getImportStatements = "from sklearn.linear_model import LogisticRegression" + + @JsonIgnore + def getUserFriendlyModelName = "Logistic Regression Training" + + override def generatePythonCode(): String = + s"""$getImportStatements + |from sklearn.pipeline import make_pipeline + |from sklearn.feature_extraction.text import CountVectorizer, TfidfTransformer + |import numpy as np + |from pytexera import * + |class ProcessTableOperator(UDFTableOperator): + | @overrides + | def process_table(self, table: Table, port: int) -> Iterator[Optional[TableLike]]: + | Y = table["$target"] + | X = table.drop("$target", axis=1) + | X = ${if (countVectorizer) "X['" + text + "']" else "X"} + | model = make_pipeline(${if (countVectorizer) "CountVectorizer()," else ""} ${if (tfidfTransformer) "TfidfTransformer()," else ""} ${getImportStatements.split(" ").last}()).fit(X, Y) + | yield {"model_name" : "$getUserFriendlyModelName", "model" : model} + | + | """.stripMargin + + + override def operatorInfo: OperatorInfo = + OperatorInfo( + getUserFriendlyModelName, + "Sklearn " + getUserFriendlyModelName + " Operator", + OperatorGroupConstants.SKLEARN_GROUP, + inputPorts = List(InputPort(PortIdentity(), "training")), + outputPorts = List(OutputPort(blocking = true)) + ) + + override def getOutputSchemas( + inputSchemas: Map[PortIdentity, Schema] + ): Map[PortIdentity, Schema] = { + Map( + operatorInfo.outputPorts.head.id -> Schema() + .add("model_name", AttributeType.STRING) + .add("model", AttributeType.BINARY) + ) + } +} diff --git a/core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/sleep/SleepOpDesc.scala b/core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/sleep/SleepOpDesc.scala index 6edcc502c27..c7f394c198b 100644 --- a/core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/sleep/SleepOpDesc.scala +++ b/core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/sleep/SleepOpDesc.scala @@ -31,7 +31,7 @@ import edu.uci.ics.amber.util.JSONUtils.objectMapper class SleepOpDesc extends LogicalOp { @JsonProperty(required = true) - @JsonSchemaTitle("Sleep") + @JsonSchemaTitle("n") var time: Int = _ override def getPhysicalOp( @@ -58,7 +58,7 @@ class SleepOpDesc extends LogicalOp { override def operatorInfo: OperatorInfo = OperatorInfo( "Sleep", - "Limit the number of output rows", + "Sleep n seconds between each tuple", OperatorGroupConstants.CONTROL_GROUP, inputPorts = List(InputPort()), outputPorts = List(OutputPort()) From 0521c1ee54adc99bb0a317406f259c5fd2952cb9 Mon Sep 17 00:00:00 2001 From: Xinyuan Lin Date: Wed, 2 Jul 2025 20:43:45 -0700 Subject: [PATCH 29/39] update --- .../uci/ics/amber/operator/LogicalOp.scala | 25 +++++-------------- 1 file changed, 6 insertions(+), 19 deletions(-) diff --git a/core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/LogicalOp.scala b/core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/LogicalOp.scala index 6063cff70fe..ba01621e279 100644 --- a/core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/LogicalOp.scala +++ b/core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/LogicalOp.scala @@ -24,11 +24,7 @@ import com.fasterxml.jackson.annotation._ import com.kjetland.jackson.jsonSchema.annotations.JsonSchemaTitle import edu.uci.ics.amber.core.executor.OperatorExecutor import edu.uci.ics.amber.core.tuple.Schema -import edu.uci.ics.amber.core.virtualidentity.{ - ExecutionIdentity, - OperatorIdentity, - WorkflowIdentity -} +import edu.uci.ics.amber.core.virtualidentity.{ExecutionIdentity, OperatorIdentity, WorkflowIdentity} import edu.uci.ics.amber.core.workflow.WorkflowContext.{DEFAULT_EXECUTION_ID, DEFAULT_WORKFLOW_ID} import edu.uci.ics.amber.core.workflow.{PhysicalOp, PhysicalPlan, PortIdentity} import edu.uci.ics.amber.operator.aggregate.AggregateOpDesc @@ -39,12 +35,7 @@ import edu.uci.ics.amber.operator.distinct.DistinctOpDesc import edu.uci.ics.amber.operator.dummy.DummyOpDesc import edu.uci.ics.amber.operator.filter.SpecializedFilterOpDesc import edu.uci.ics.amber.operator.hashJoin.HashJoinOpDesc -import edu.uci.ics.amber.operator.huggingFace.{ - HuggingFaceIrisLogisticRegressionOpDesc, - HuggingFaceSentimentAnalysisOpDesc, - HuggingFaceSpamSMSDetectionOpDesc, - HuggingFaceTextSummarizationOpDesc -} +import edu.uci.ics.amber.operator.huggingFace.{HuggingFaceIrisLogisticRegressionOpDesc, HuggingFaceSentimentAnalysisOpDesc, HuggingFaceSpamSMSDetectionOpDesc, HuggingFaceTextSummarizationOpDesc} import edu.uci.ics.amber.operator.ifStatement.IfOpDesc import edu.uci.ics.amber.operator.intersect.IntersectOpDesc import edu.uci.ics.amber.operator.intervalJoin.IntervalJoinOpDesc @@ -52,10 +43,7 @@ import edu.uci.ics.amber.operator.keywordSearch.KeywordSearchOpDesc import edu.uci.ics.amber.operator.limit.LimitOpDesc import edu.uci.ics.amber.operator.loop.{LoopEndOpDesc, LoopStartOpDesc} import edu.uci.ics.amber.operator.machineLearning.Scorer.MachineLearningScorerOpDesc -import edu.uci.ics.amber.operator.machineLearning.sklearnAdvanced.KNNTrainer.{ - SklearnAdvancedKNNClassifierTrainerOpDesc, - SklearnAdvancedKNNRegressorTrainerOpDesc -} +import edu.uci.ics.amber.operator.machineLearning.sklearnAdvanced.KNNTrainer.{SklearnAdvancedKNNClassifierTrainerOpDesc, SklearnAdvancedKNNRegressorTrainerOpDesc} import edu.uci.ics.amber.operator.machineLearning.sklearnAdvanced.SVCTrainer.SklearnAdvancedSVCTrainerOpDesc import edu.uci.ics.amber.operator.machineLearning.sklearnAdvanced.SVRTrainer.SklearnAdvancedSVRTrainerOpDesc import edu.uci.ics.amber.operator.metadata.{OPVersion, OperatorInfo, PropertyNameConstants} @@ -64,14 +52,12 @@ import edu.uci.ics.amber.operator.randomksampling.RandomKSamplingOpDesc import edu.uci.ics.amber.operator.regex.RegexOpDesc import edu.uci.ics.amber.operator.reservoirsampling.ReservoirSamplingOpDesc import edu.uci.ics.amber.operator.sklearn._ +import edu.uci.ics.amber.operator.sklearn.training.SklearnLRTrainingClassifierOpDesc import edu.uci.ics.amber.operator.sleep.SleepOpDesc import edu.uci.ics.amber.operator.sort.SortOpDesc import edu.uci.ics.amber.operator.sortPartitions.SortPartitionsOpDesc import edu.uci.ics.amber.operator.source.apis.reddit.RedditSearchSourceOpDesc -import edu.uci.ics.amber.operator.source.apis.twitter.v2.{ - TwitterFullArchiveSearchSourceOpDesc, - TwitterSearchSourceOpDesc -} +import edu.uci.ics.amber.operator.source.apis.twitter.v2.{TwitterFullArchiveSearchSourceOpDesc, TwitterSearchSourceOpDesc} import edu.uci.ics.amber.operator.source.fetcher.URLFetcherOpDesc import edu.uci.ics.amber.operator.source.scan.FileScanSourceOpDesc import edu.uci.ics.amber.operator.source.scan.arrow.ArrowSourceOpDesc @@ -267,6 +253,7 @@ trait StateTransferFunc new Type(value = classOf[SklearnBaggingOpDesc], name = "SklearnBagging"), new Type(value = classOf[SklearnGradientBoostingOpDesc], name = "SklearnGradientBoosting"), new Type(value = classOf[SklearnAdaptiveBoostingOpDesc], name = "SklearnAdaptiveBoosting"), + new Type(value = classOf[SklearnLRTrainingClassifierOpDesc], name = "SklearnLRTraining"), new Type(value = classOf[SklearnExtraTreesOpDesc], name = "SklearnExtraTrees"), new Type(value = classOf[SklearnGaussianNaiveBayesOpDesc], name = "SklearnGaussianNaiveBayes"), new Type( From 014656c6a8710f8ef31b16cd19cd02c5a8b0dd68 Mon Sep 17 00:00:00 2001 From: Xinyuan Lin Date: Wed, 2 Jul 2025 20:43:58 -0700 Subject: [PATCH 30/39] update --- core/amber/src/main/python/core/models/operator.py | 1 + 1 file changed, 1 insertion(+) diff --git a/core/amber/src/main/python/core/models/operator.py b/core/amber/src/main/python/core/models/operator.py index fcbf642575b..53d11856575 100644 --- a/core/amber/src/main/python/core/models/operator.py +++ b/core/amber/src/main/python/core/models/operator.py @@ -238,6 +238,7 @@ def process_tuple(self, tuple_: Tuple, port: int) -> Iterator[Optional[TupleLike def on_finish(self, port: int) -> Iterator[Optional[TableLike]]: table = Table(self.__table_data[port]) + self.__table_data.clear() yield from self.process_table(table, port) @abstractmethod From bc5b05c9a6340b6dce55bd8e4211c1f1f56ac403 Mon Sep 17 00:00:00 2001 From: Xinyuan Lin Date: Wed, 2 Jul 2025 22:42:51 -0700 Subject: [PATCH 31/39] update --- .../messaginglayer/OutputManager.scala | 5 + .../promisehandlers/EndChannelHandler.scala | 17 +--- .../promisehandlers/EndIterationHandler.scala | 2 +- .../NextIterationHandler.scala | 15 +-- .../operator_images/SklearnRFTraining.png | Bin 0 -> 81937 bytes .../assets/operator_images/SklearnTesting.png | Bin 0 -> 98115 bytes .../uci/ics/amber/operator/LogicalOp.scala | 6 +- .../testing/SklearnTestingOpDesc.scala | 93 ++++++++++++++++++ ...> SklearnRFTrainingClassifierOpDesc.scala} | 6 +- 9 files changed, 114 insertions(+), 30 deletions(-) create mode 100644 core/gui/src/assets/operator_images/SklearnRFTraining.png create mode 100644 core/gui/src/assets/operator_images/SklearnTesting.png create mode 100644 core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/sklearn/testing/SklearnTestingOpDesc.scala rename core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/sklearn/training/{SklearnLRTrainingClassifierOpDesc.scala => SklearnRFTrainingClassifierOpDesc.scala} (95%) diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/messaginglayer/OutputManager.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/messaginglayer/OutputManager.scala index 023c6b5738f..027d749f9ef 100644 --- a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/messaginglayer/OutputManager.scala +++ b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/messaginglayer/OutputManager.scala @@ -259,6 +259,11 @@ class OutputManager( outputIterator.appendSpecialTupleToEnd(FinalizeExecutor()) } + def finalizeIteration(worker: ActorVirtualIdentity): Unit = { + outputIterator.appendSpecialTupleToEnd(FinalizeIteration(worker)) + } + + /** * This method is only used for ensuring correct region execution. Some operators may have input port dependency * relationships, for which we currently use a two-phase region execution scheme. (See `RegionExecutionCoordinator` diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/EndChannelHandler.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/EndChannelHandler.scala index 9b012737480..d5b3a096aec 100644 --- a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/EndChannelHandler.scala +++ b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/EndChannelHandler.scala @@ -20,13 +20,9 @@ package edu.uci.ics.amber.engine.architecture.worker.promisehandlers import com.twitter.util.Future -import edu.uci.ics.amber.core.tuple.FinalizePort +import edu.uci.ics.amber.core.tuple.{FinalizeIteration, FinalizePort} import edu.uci.ics.amber.engine.architecture.rpc.controlcommands.EmbeddedControlMessageType.PORT_ALIGNMENT -import edu.uci.ics.amber.engine.architecture.rpc.controlcommands.{ - AsyncRPCContext, - EmptyRequest, - EndIterationRequest -} +import edu.uci.ics.amber.engine.architecture.rpc.controlcommands.{AsyncRPCContext, EmptyRequest, EndIterationRequest} import edu.uci.ics.amber.engine.architecture.rpc.controlreturns.EmptyReturn import edu.uci.ics.amber.engine.architecture.rpc.workerservice.WorkerServiceGrpc.METHOD_END_ITERATION import edu.uci.ics.amber.engine.architecture.worker.DataProcessorRPCHandlerInitializer @@ -53,14 +49,9 @@ trait EndChannelHandler { // See documentation of isMissingOutputPort if (!dp.outputManager.isMissingOutputPort) { dp.executor match { - case _: LoopStartOpExec => - dp.sendECMToDataChannels( - METHOD_END_ITERATION.getBareMethodName, - PORT_ALIGNMENT, - EndIterationRequest(dp.actorId) - ) + case executor: LoopStartOpExec if executor.checkCondition() => + dp.outputManager.finalizeIteration(dp.actorId) case _ => - // assuming all the output ports finalize after all input ports are finalized. dp.outputManager.finalizeOutput() } } diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/EndIterationHandler.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/EndIterationHandler.scala index dab0cfa747d..cca2d0a8f14 100644 --- a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/EndIterationHandler.scala +++ b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/EndIterationHandler.scala @@ -42,7 +42,7 @@ trait EndIterationHandler { workerInterface.nextIteration(EmptyRequest(), mkContext(request.worker)) case _ => dp.processOnFinish() - dp.outputManager.outputIterator.appendSpecialTupleToEnd(FinalizeIteration(request.worker)) + dp.outputManager.finalizeIteration(request.worker) } EmptyReturn() } diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/NextIterationHandler.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/NextIterationHandler.scala index fbfd2e30247..9285369e6ad 100644 --- a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/NextIterationHandler.scala +++ b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/NextIterationHandler.scala @@ -20,12 +20,9 @@ package edu.uci.ics.amber.engine.architecture.worker.promisehandlers import com.twitter.util.Future +import edu.uci.ics.amber.core.tuple.FinalizeIteration import edu.uci.ics.amber.engine.architecture.rpc.controlcommands.EmbeddedControlMessageType.PORT_ALIGNMENT -import edu.uci.ics.amber.engine.architecture.rpc.controlcommands.{ - AsyncRPCContext, - EmptyRequest, - EndIterationRequest -} +import edu.uci.ics.amber.engine.architecture.rpc.controlcommands.{AsyncRPCContext, EmptyRequest, EndIterationRequest} import edu.uci.ics.amber.engine.architecture.rpc.controlreturns.EmptyReturn import edu.uci.ics.amber.engine.architecture.rpc.workerservice.WorkerServiceGrpc.METHOD_END_ITERATION import edu.uci.ics.amber.engine.architecture.worker.DataProcessorRPCHandlerInitializer @@ -38,13 +35,9 @@ trait NextIterationHandler { request: EmptyRequest, ctx: AsyncRPCContext ): Future[EmptyReturn] = { + dp.processOnFinish() if (dp.executor.asInstanceOf[LoopStartOpExec].checkCondition()) { - dp.processOnFinish() - dp.sendECMToDataChannels( - METHOD_END_ITERATION.getBareMethodName, - PORT_ALIGNMENT, - EndIterationRequest(dp.actorId) - ) + dp.outputManager.finalizeIteration(dp.actorId) } else { dp.outputManager.finalizeOutput() } diff --git a/core/gui/src/assets/operator_images/SklearnRFTraining.png b/core/gui/src/assets/operator_images/SklearnRFTraining.png new file mode 100644 index 0000000000000000000000000000000000000000..2ba59197dc8e13c51f4c712ab673bc1b5893e6d6 GIT binary patch literal 81937 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�hHl;lIi1KB-rMXv&j{cYbP_~3Z)>P+ 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 literal 0 HcmV?d00001 diff --git a/core/gui/src/assets/operator_images/SklearnTesting.png b/core/gui/src/assets/operator_images/SklearnTesting.png new file mode 100644 index 0000000000000000000000000000000000000000..622f9a71dd26e795db49ad55aa4d46e262c67b0a GIT binary patch literal 98115 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ธzEjeiF{5 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 Date: Sun, 6 Jul 2025 18:21:59 -0700 Subject: [PATCH 34/39] fix fmt --- .../messaginglayer/OutputManager.scala | 1 - .../promisehandlers/EndChannelHandler.scala | 6 ++++- .../NextIterationHandler.scala | 6 ++++- .../uci/ics/amber/operator/LogicalOp.scala | 27 +++++++++++++------ 4 files changed, 29 insertions(+), 11 deletions(-) diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/messaginglayer/OutputManager.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/messaginglayer/OutputManager.scala index 027d749f9ef..c742b72c995 100644 --- a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/messaginglayer/OutputManager.scala +++ b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/messaginglayer/OutputManager.scala @@ -263,7 +263,6 @@ class OutputManager( outputIterator.appendSpecialTupleToEnd(FinalizeIteration(worker)) } - /** * This method is only used for ensuring correct region execution. Some operators may have input port dependency * relationships, for which we currently use a two-phase region execution scheme. (See `RegionExecutionCoordinator` diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/EndChannelHandler.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/EndChannelHandler.scala index d5b3a096aec..465a1e179e0 100644 --- a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/EndChannelHandler.scala +++ b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/EndChannelHandler.scala @@ -22,7 +22,11 @@ package edu.uci.ics.amber.engine.architecture.worker.promisehandlers import com.twitter.util.Future import edu.uci.ics.amber.core.tuple.{FinalizeIteration, FinalizePort} import edu.uci.ics.amber.engine.architecture.rpc.controlcommands.EmbeddedControlMessageType.PORT_ALIGNMENT -import edu.uci.ics.amber.engine.architecture.rpc.controlcommands.{AsyncRPCContext, EmptyRequest, EndIterationRequest} +import edu.uci.ics.amber.engine.architecture.rpc.controlcommands.{ + AsyncRPCContext, + EmptyRequest, + EndIterationRequest +} import edu.uci.ics.amber.engine.architecture.rpc.controlreturns.EmptyReturn import edu.uci.ics.amber.engine.architecture.rpc.workerservice.WorkerServiceGrpc.METHOD_END_ITERATION import edu.uci.ics.amber.engine.architecture.worker.DataProcessorRPCHandlerInitializer diff --git a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/NextIterationHandler.scala b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/NextIterationHandler.scala index 9285369e6ad..5a55a2eb615 100644 --- a/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/NextIterationHandler.scala +++ b/core/amber/src/main/scala/edu/uci/ics/amber/engine/architecture/worker/promisehandlers/NextIterationHandler.scala @@ -22,7 +22,11 @@ package edu.uci.ics.amber.engine.architecture.worker.promisehandlers import com.twitter.util.Future import edu.uci.ics.amber.core.tuple.FinalizeIteration import edu.uci.ics.amber.engine.architecture.rpc.controlcommands.EmbeddedControlMessageType.PORT_ALIGNMENT -import edu.uci.ics.amber.engine.architecture.rpc.controlcommands.{AsyncRPCContext, EmptyRequest, EndIterationRequest} +import edu.uci.ics.amber.engine.architecture.rpc.controlcommands.{ + AsyncRPCContext, + EmptyRequest, + EndIterationRequest +} import edu.uci.ics.amber.engine.architecture.rpc.controlreturns.EmptyReturn import edu.uci.ics.amber.engine.architecture.rpc.workerservice.WorkerServiceGrpc.METHOD_END_ITERATION import edu.uci.ics.amber.engine.architecture.worker.DataProcessorRPCHandlerInitializer diff --git a/core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/LogicalOp.scala b/core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/LogicalOp.scala index 32cf50b0e5d..7d4e0de868a 100644 --- a/core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/LogicalOp.scala +++ b/core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/LogicalOp.scala @@ -24,7 +24,11 @@ import com.fasterxml.jackson.annotation._ import com.kjetland.jackson.jsonSchema.annotations.JsonSchemaTitle import edu.uci.ics.amber.core.executor.OperatorExecutor import edu.uci.ics.amber.core.tuple.Schema -import edu.uci.ics.amber.core.virtualidentity.{ExecutionIdentity, OperatorIdentity, WorkflowIdentity} +import edu.uci.ics.amber.core.virtualidentity.{ + ExecutionIdentity, + OperatorIdentity, + WorkflowIdentity +} import edu.uci.ics.amber.core.workflow.WorkflowContext.{DEFAULT_EXECUTION_ID, DEFAULT_WORKFLOW_ID} import edu.uci.ics.amber.core.workflow.{PhysicalOp, PhysicalPlan, PortIdentity} import edu.uci.ics.amber.operator.aggregate.AggregateOpDesc @@ -35,7 +39,12 @@ import edu.uci.ics.amber.operator.distinct.DistinctOpDesc import edu.uci.ics.amber.operator.dummy.DummyOpDesc import edu.uci.ics.amber.operator.filter.SpecializedFilterOpDesc import edu.uci.ics.amber.operator.hashJoin.HashJoinOpDesc -import edu.uci.ics.amber.operator.huggingFace.{HuggingFaceIrisLogisticRegressionOpDesc, HuggingFaceSentimentAnalysisOpDesc, HuggingFaceSpamSMSDetectionOpDesc, HuggingFaceTextSummarizationOpDesc} +import edu.uci.ics.amber.operator.huggingFace.{ + HuggingFaceIrisLogisticRegressionOpDesc, + HuggingFaceSentimentAnalysisOpDesc, + HuggingFaceSpamSMSDetectionOpDesc, + HuggingFaceTextSummarizationOpDesc +} import edu.uci.ics.amber.operator.ifStatement.IfOpDesc import edu.uci.ics.amber.operator.intersect.IntersectOpDesc import edu.uci.ics.amber.operator.intervalJoin.IntervalJoinOpDesc @@ -43,7 +52,10 @@ import edu.uci.ics.amber.operator.keywordSearch.KeywordSearchOpDesc import edu.uci.ics.amber.operator.limit.LimitOpDesc import edu.uci.ics.amber.operator.loop.{LoopEndOpDesc, LoopStartOpDesc} import edu.uci.ics.amber.operator.machineLearning.Scorer.MachineLearningScorerOpDesc -import edu.uci.ics.amber.operator.machineLearning.sklearnAdvanced.KNNTrainer.{SklearnAdvancedKNNClassifierTrainerOpDesc, SklearnAdvancedKNNRegressorTrainerOpDesc} +import edu.uci.ics.amber.operator.machineLearning.sklearnAdvanced.KNNTrainer.{ + SklearnAdvancedKNNClassifierTrainerOpDesc, + SklearnAdvancedKNNRegressorTrainerOpDesc +} import edu.uci.ics.amber.operator.machineLearning.sklearnAdvanced.SVCTrainer.SklearnAdvancedSVCTrainerOpDesc import edu.uci.ics.amber.operator.machineLearning.sklearnAdvanced.SVRTrainer.SklearnAdvancedSVRTrainerOpDesc import edu.uci.ics.amber.operator.metadata.{OPVersion, OperatorInfo, PropertyNameConstants} @@ -52,13 +64,14 @@ import edu.uci.ics.amber.operator.randomksampling.RandomKSamplingOpDesc import edu.uci.ics.amber.operator.regex.RegexOpDesc import edu.uci.ics.amber.operator.reservoirsampling.ReservoirSamplingOpDesc import edu.uci.ics.amber.operator.sklearn._ -import edu.uci.ics.amber.operator.sklearn.testing.SklearnTestingOpDesc -import edu.uci.ics.amber.operator.sklearn.training.SklearnRFTrainingClassifierOpDesc import edu.uci.ics.amber.operator.sleep.SleepOpDesc import edu.uci.ics.amber.operator.sort.SortOpDesc import edu.uci.ics.amber.operator.sortPartitions.SortPartitionsOpDesc import edu.uci.ics.amber.operator.source.apis.reddit.RedditSearchSourceOpDesc -import edu.uci.ics.amber.operator.source.apis.twitter.v2.{TwitterFullArchiveSearchSourceOpDesc, TwitterSearchSourceOpDesc} +import edu.uci.ics.amber.operator.source.apis.twitter.v2.{ + TwitterFullArchiveSearchSourceOpDesc, + TwitterSearchSourceOpDesc +} import edu.uci.ics.amber.operator.source.fetcher.URLFetcherOpDesc import edu.uci.ics.amber.operator.source.scan.FileScanSourceOpDesc import edu.uci.ics.amber.operator.source.scan.arrow.ArrowSourceOpDesc @@ -256,7 +269,6 @@ trait StateTransferFunc new Type(value = classOf[SklearnBaggingOpDesc], name = "SklearnBagging"), new Type(value = classOf[SklearnGradientBoostingOpDesc], name = "SklearnGradientBoosting"), new Type(value = classOf[SklearnAdaptiveBoostingOpDesc], name = "SklearnAdaptiveBoosting"), - new Type(value = classOf[SklearnRFTrainingClassifierOpDesc], name = "SklearnRFTraining"), new Type(value = classOf[SklearnExtraTreesOpDesc], name = "SklearnExtraTrees"), new Type(value = classOf[SklearnGaussianNaiveBayesOpDesc], name = "SklearnGaussianNaiveBayes"), new Type( @@ -273,7 +285,6 @@ trait StateTransferFunc ), new Type(value = classOf[SklearnDummyClassifierOpDesc], name = "SklearnDummyClassifier"), new Type(value = classOf[SklearnPredictionOpDesc], name = "SklearnPrediction"), - new Type(value = classOf[SklearnTestingOpDesc], name = "SklearnTesting"), new Type( value = classOf[HuggingFaceSentimentAnalysisOpDesc], name = "HuggingFaceSentimentAnalysis" From 39d7a0382998a1ec8404c3c5bd6602bcdb87bcec Mon Sep 17 00:00:00 2001 From: Xinyuan Lin Date: Sun, 6 Jul 2025 18:23:32 -0700 Subject: [PATCH 35/39] fix fmt --- .../scala/edu/uci/ics/amber/operator/loop/LoopStartOpExec.scala | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/loop/LoopStartOpExec.scala b/core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/loop/LoopStartOpExec.scala index b4b60ee9740..08dd4b53b5c 100644 --- a/core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/loop/LoopStartOpExec.scala +++ b/core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/loop/LoopStartOpExec.scala @@ -42,4 +42,6 @@ class LoopStartOpExec(descString: String) extends OperatorExecutor { currentIteration += 1 data.iterator } + + override def close(): Unit = data.clear() } From d766dbc7610d16293df3b4900446c222009a3b28 Mon Sep 17 00:00:00 2001 From: Xinyuan Lin Date: Sun, 6 Jul 2025 18:24:17 -0700 Subject: [PATCH 36/39] Update core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/loop/LoopStartOpDesc.scala Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Xinyuan Lin --- .../scala/edu/uci/ics/amber/operator/loop/LoopStartOpDesc.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/loop/LoopStartOpDesc.scala b/core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/loop/LoopStartOpDesc.scala index 79204dc7dcf..561da5fee88 100644 --- a/core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/loop/LoopStartOpDesc.scala +++ b/core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/loop/LoopStartOpDesc.scala @@ -51,6 +51,7 @@ class LoopStartOpDesc extends LogicalOp { .withInputPorts(operatorInfo.inputPorts) .withOutputPorts(operatorInfo.outputPorts) .withSuggestedWorkerNum(1) + .withParallelizable(false) } override def operatorInfo: OperatorInfo = From e283da5fe1247e6e44519801781e583dc468c01c Mon Sep 17 00:00:00 2001 From: Xinyuan Lin Date: Sun, 6 Jul 2025 18:27:02 -0700 Subject: [PATCH 37/39] fix fmt --- .../core/architecture/handlers/control/end_iteration_handler.py | 1 - 1 file changed, 1 deletion(-) diff --git a/core/amber/src/main/python/core/architecture/handlers/control/end_iteration_handler.py b/core/amber/src/main/python/core/architecture/handlers/control/end_iteration_handler.py index df9e7a2a77d..7351b6771ed 100644 --- a/core/amber/src/main/python/core/architecture/handlers/control/end_iteration_handler.py +++ b/core/amber/src/main/python/core/architecture/handlers/control/end_iteration_handler.py @@ -25,7 +25,6 @@ class EndIterationHandler(ControlHandler): async def end_iteration(self, req: EndIterationRequest) -> EmptyReturn: - print("ergerhgerherh") self.context.tuple_processing_manager.current_internal_marker = EndIteration( req.worker ) From 747e636e05ef96733730ceb42837f749141e8cdb Mon Sep 17 00:00:00 2001 From: Xinyuan Lin Date: Sun, 6 Jul 2025 18:49:08 -0700 Subject: [PATCH 38/39] fix fmt --- core/amber/src/main/python/core/models/operator.py | 7 +++++++ core/amber/src/main/python/core/runnables/main_loop.py | 3 +-- .../edu/uci/ics/amber/operator/loop/LoopStartOpExec.scala | 1 - 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/core/amber/src/main/python/core/models/operator.py b/core/amber/src/main/python/core/models/operator.py index 53d11856575..3c10a0b727d 100644 --- a/core/amber/src/main/python/core/models/operator.py +++ b/core/amber/src/main/python/core/models/operator.py @@ -64,6 +64,13 @@ def close(self) -> None: """ pass + def reset(self) -> None: + """ + Reset the operator to its initial state. + """ + self.close() + self.open() + def process_state(self, state: State, port: int) -> Optional[State]: """ Process an input State from the given link. diff --git a/core/amber/src/main/python/core/runnables/main_loop.py b/core/amber/src/main/python/core/runnables/main_loop.py index f9b921820a2..2b4b0fd9027 100644 --- a/core/amber/src/main/python/core/runnables/main_loop.py +++ b/core/amber/src/main/python/core/runnables/main_loop.py @@ -295,8 +295,7 @@ def _process_end_iteration(self) -> None: EmbeddedControlMessageType.PORT_ALIGNMENT, EndIterationRequest(worker_id), ) - self.context.executor_manager.executor.close() - self.context.executor_manager.executor.open() + self.context.executor_manager.executor.reset() def _process_ecm(self, ecm_element: ECMElement): """ diff --git a/core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/loop/LoopStartOpExec.scala b/core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/loop/LoopStartOpExec.scala index 08dd4b53b5c..edc4b049247 100644 --- a/core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/loop/LoopStartOpExec.scala +++ b/core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/loop/LoopStartOpExec.scala @@ -43,5 +43,4 @@ class LoopStartOpExec(descString: String) extends OperatorExecutor { data.iterator } - override def close(): Unit = data.clear() } From 39251acb7eba66d86c1bbd4cbeb0845fb5260fb6 Mon Sep 17 00:00:00 2001 From: Xinyuan Lin Date: Sun, 6 Jul 2025 19:04:16 -0700 Subject: [PATCH 39/39] remove sleep --- core/gui/src/assets/operator_images/Sleep.png | Bin 59428 -> 0 bytes .../uci/ics/amber/operator/LogicalOp.scala | 2 - .../amber/operator/sleep/SleepOpDesc.scala | 66 ------------------ .../amber/operator/sleep/SleepOpExec.scala | 33 --------- 4 files changed, 101 deletions(-) delete mode 100644 core/gui/src/assets/operator_images/Sleep.png delete mode 100644 core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/sleep/SleepOpDesc.scala delete mode 100644 core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/sleep/SleepOpExec.scala diff --git a/core/gui/src/assets/operator_images/Sleep.png b/core/gui/src/assets/operator_images/Sleep.png deleted file mode 100644 index 4a21e236ef65f3b711948018a578de9c2cba1f83..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 59428 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 H^uzxE#w|6S diff --git a/core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/LogicalOp.scala b/core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/LogicalOp.scala index 7d4e0de868a..a21cf29adc4 100644 --- a/core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/LogicalOp.scala +++ b/core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/LogicalOp.scala @@ -64,7 +64,6 @@ import edu.uci.ics.amber.operator.randomksampling.RandomKSamplingOpDesc import edu.uci.ics.amber.operator.regex.RegexOpDesc import edu.uci.ics.amber.operator.reservoirsampling.ReservoirSamplingOpDesc import edu.uci.ics.amber.operator.sklearn._ -import edu.uci.ics.amber.operator.sleep.SleepOpDesc import edu.uci.ics.amber.operator.sort.SortOpDesc import edu.uci.ics.amber.operator.sortPartitions.SortPartitionsOpDesc import edu.uci.ics.amber.operator.source.apis.reddit.RedditSearchSourceOpDesc @@ -189,7 +188,6 @@ trait StateTransferFunc new Type(value = classOf[AsterixDBSourceOpDesc], name = "AsterixDBSource"), new Type(value = classOf[TypeCastingOpDesc], name = "TypeCasting"), new Type(value = classOf[LimitOpDesc], name = "Limit"), - new Type(value = classOf[SleepOpDesc], name = "Sleep"), new Type(value = classOf[LoopStartOpDesc], name = "LoopStart"), new Type(value = classOf[LoopEndOpDesc], name = "LoopEnd"), new Type(value = classOf[RandomKSamplingOpDesc], name = "RandomKSampling"), diff --git a/core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/sleep/SleepOpDesc.scala b/core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/sleep/SleepOpDesc.scala deleted file mode 100644 index c7f394c198b..00000000000 --- a/core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/sleep/SleepOpDesc.scala +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package edu.uci.ics.amber.operator.sleep - -import com.fasterxml.jackson.annotation.JsonProperty -import com.kjetland.jackson.jsonSchema.annotations.JsonSchemaTitle -import edu.uci.ics.amber.core.executor.OpExecWithClassName -import edu.uci.ics.amber.core.virtualidentity.{ExecutionIdentity, WorkflowIdentity} -import edu.uci.ics.amber.core.workflow.{InputPort, OutputPort, PhysicalOp} -import edu.uci.ics.amber.operator.LogicalOp -import edu.uci.ics.amber.operator.metadata.{OperatorGroupConstants, OperatorInfo} -import edu.uci.ics.amber.util.JSONUtils.objectMapper - -class SleepOpDesc extends LogicalOp { - - @JsonProperty(required = true) - @JsonSchemaTitle("n") - var time: Int = _ - - override def getPhysicalOp( - workflowId: WorkflowIdentity, - executionId: ExecutionIdentity - ): PhysicalOp = { - PhysicalOp - .oneToOnePhysicalOp( - workflowId, - executionId, - operatorIdentifier, - OpExecWithClassName( - "edu.uci.ics.amber.operator.sleep.SleepOpExec", - objectMapper.writeValueAsString(this) - ) - ) - .withInputPorts(operatorInfo.inputPorts) - .withOutputPorts(operatorInfo.outputPorts) - .withParallelizable(false) - .withSuggestedWorkerNum(1) - - } - - override def operatorInfo: OperatorInfo = - OperatorInfo( - "Sleep", - "Sleep n seconds between each tuple", - OperatorGroupConstants.CONTROL_GROUP, - inputPorts = List(InputPort()), - outputPorts = List(OutputPort()) - ) -} diff --git a/core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/sleep/SleepOpExec.scala b/core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/sleep/SleepOpExec.scala deleted file mode 100644 index e7eb1c232d3..00000000000 --- a/core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/sleep/SleepOpExec.scala +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package edu.uci.ics.amber.operator.sleep - -import edu.uci.ics.amber.core.executor.OperatorExecutor -import edu.uci.ics.amber.core.tuple.{Tuple, TupleLike} -import edu.uci.ics.amber.util.JSONUtils.objectMapper - -class SleepOpExec(descString: String) extends OperatorExecutor { - private val desc: SleepOpDesc = objectMapper.readValue(descString, classOf[SleepOpDesc]) - - override def processTuple(tuple: Tuple, port: Int): Iterator[TupleLike] = { - Thread.sleep(1000 * desc.time) - Iterator(tuple) - } -}

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 None: + | self.data = [] + | @overrides + | def process_tuple(self, tuple_: Tuple, port: int) -> Iterator[Optional[TupleLike]]: + | if port == 0: + | self.data.append(tuple_) + | else: + | model = tuple_["$model"] + | table = Table(self.data) + | Y = table["$target"] + | X = table.drop("$target", axis=1) + | + | predictions = model.predict(X) + | + | tuple_["accuracy"] = round(accuracy_score(Y, predictions), 4) + | tuple_["f1"] = f1_score(Y, predictions) + | tuple_["precision"] = precision_score(Y, predictions) + | tuple_["recall"] = recall_score(Y, predictions) + | yield tuple_""".stripMargin + + override def operatorInfo: OperatorInfo = + OperatorInfo( + "Sklearn Testing", + "Skleanr Testing Operator", + OperatorGroupConstants.SKLEARN_GROUP, + inputPorts = List( + InputPort(PortIdentity(), "data"), + InputPort(PortIdentity(1),"model", dependencies = List(PortIdentity())) + ), + outputPorts = List(OutputPort()) + ) + + override def getOutputSchemas( + inputSchemas: Map[PortIdentity, Schema] + ): Map[PortIdentity, Schema] = { + val inputSchema = inputSchemas(operatorInfo.inputPorts(1).id) + Map( + operatorInfo.outputPorts.head.id -> inputSchema + .add("accuracy", AttributeType.DOUBLE) + .add("f1", AttributeType.DOUBLE) + .add("precision", AttributeType.DOUBLE) + .add("recall", AttributeType.DOUBLE) + ) + } +} diff --git a/core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/sklearn/training/SklearnLRTrainingClassifierOpDesc.scala b/core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/sklearn/training/SklearnRFTrainingClassifierOpDesc.scala similarity index 95% rename from core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/sklearn/training/SklearnLRTrainingClassifierOpDesc.scala rename to core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/sklearn/training/SklearnRFTrainingClassifierOpDesc.scala index 5ff1c029c39..c229458e3e0 100644 --- a/core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/sklearn/training/SklearnLRTrainingClassifierOpDesc.scala +++ b/core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/sklearn/training/SklearnRFTrainingClassifierOpDesc.scala @@ -36,7 +36,7 @@ import edu.uci.ics.amber.operator.metadata.annotations.{ } import edu.uci.ics.amber.operator.metadata.{OperatorGroupConstants, OperatorInfo} -class SklearnLRTrainingClassifierOpDesc extends PythonOperatorDescriptor { +class SklearnRFTrainingClassifierOpDesc extends PythonOperatorDescriptor { @JsonSchemaTitle("Target Attribute") @JsonPropertyDescription("Attribute in your dataset corresponding to target.") @@ -80,10 +80,10 @@ class SklearnLRTrainingClassifierOpDesc extends PythonOperatorDescriptor { val tfidfTransformer: Boolean = false @JsonIgnore - def getImportStatements = "from sklearn.linear_model import LogisticRegression" + def getImportStatements = "from sklearn.ensemble import RandomForestClassifier" @JsonIgnore - def getUserFriendlyModelName = "Logistic Regression Training" + def getUserFriendlyModelName = "RandomForest Training" override def generatePythonCode(): String = s"""$getImportStatements From 1151bd8da555afc411a4a3da56790bf5468ca867 Mon Sep 17 00:00:00 2001 From: Xinyuan Lin Date: Sun, 6 Jul 2025 18:15:55 -0700 Subject: [PATCH 32/39] fix fmt --- .../testing/SklearnTestingOpDesc.scala | 93 ------------- .../SklearnRFTrainingClassifierOpDesc.scala | 124 ------------------ 2 files changed, 217 deletions(-) delete mode 100644 core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/sklearn/testing/SklearnTestingOpDesc.scala delete mode 100644 core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/sklearn/training/SklearnRFTrainingClassifierOpDesc.scala diff --git a/core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/sklearn/testing/SklearnTestingOpDesc.scala b/core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/sklearn/testing/SklearnTestingOpDesc.scala deleted file mode 100644 index e2085f950f2..00000000000 --- a/core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/sklearn/testing/SklearnTestingOpDesc.scala +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package edu.uci.ics.amber.operator.sklearn.testing - -import com.fasterxml.jackson.annotation.{JsonProperty, JsonPropertyDescription} -import com.kjetland.jackson.jsonSchema.annotations.JsonSchemaTitle -import edu.uci.ics.amber.core.tuple.{AttributeType, Schema} -import edu.uci.ics.amber.core.workflow.{InputPort, OutputPort, PortIdentity} -import edu.uci.ics.amber.operator.PythonOperatorDescriptor -import edu.uci.ics.amber.operator.metadata.annotations.{AutofillAttributeName, AutofillAttributeNameOnPort1} -import edu.uci.ics.amber.operator.metadata.{OperatorGroupConstants, OperatorInfo} - -class SklearnTestingOpDesc extends PythonOperatorDescriptor { - @JsonSchemaTitle("Model Attribute") - @JsonProperty(required = true, defaultValue = "model") - @JsonPropertyDescription("Attribute corresponding to ML model") - @AutofillAttributeNameOnPort1 - var model: String = _ - - @JsonSchemaTitle("Target Attribute") - @JsonPropertyDescription("Attribute in your dataset corresponding to target.") - @JsonProperty(required = true) - @AutofillAttributeName - var target: String = _ - - override def generatePythonCode(): String = - s"""from pytexera import * - |from sklearn.metrics import accuracy_score, f1_score, precision_score, recall_score - |from sklearn.pipeline import Pipeline - |class ProcessTupleOperator(UDFOperatorV2): - | @overrides - | def open(self) -> None: - | self.data = [] - | @overrides - | def process_tuple(self, tuple_: Tuple, port: int) -> Iterator[Optional[TupleLike]]: - | if port == 0: - | self.data.append(tuple_) - | else: - | model = tuple_["$model"] - | table = Table(self.data) - | Y = table["$target"] - | X = table.drop("$target", axis=1) - | - | predictions = model.predict(X) - | - | tuple_["accuracy"] = round(accuracy_score(Y, predictions), 4) - | tuple_["f1"] = f1_score(Y, predictions) - | tuple_["precision"] = precision_score(Y, predictions) - | tuple_["recall"] = recall_score(Y, predictions) - | yield tuple_""".stripMargin - - override def operatorInfo: OperatorInfo = - OperatorInfo( - "Sklearn Testing", - "Skleanr Testing Operator", - OperatorGroupConstants.SKLEARN_GROUP, - inputPorts = List( - InputPort(PortIdentity(), "data"), - InputPort(PortIdentity(1),"model", dependencies = List(PortIdentity())) - ), - outputPorts = List(OutputPort()) - ) - - override def getOutputSchemas( - inputSchemas: Map[PortIdentity, Schema] - ): Map[PortIdentity, Schema] = { - val inputSchema = inputSchemas(operatorInfo.inputPorts(1).id) - Map( - operatorInfo.outputPorts.head.id -> inputSchema - .add("accuracy", AttributeType.DOUBLE) - .add("f1", AttributeType.DOUBLE) - .add("precision", AttributeType.DOUBLE) - .add("recall", AttributeType.DOUBLE) - ) - } -} diff --git a/core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/sklearn/training/SklearnRFTrainingClassifierOpDesc.scala b/core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/sklearn/training/SklearnRFTrainingClassifierOpDesc.scala deleted file mode 100644 index c229458e3e0..00000000000 --- a/core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/sklearn/training/SklearnRFTrainingClassifierOpDesc.scala +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package edu.uci.ics.amber.operator.sklearn.training - -import com.fasterxml.jackson.annotation.{JsonIgnore, JsonProperty, JsonPropertyDescription} -import com.kjetland.jackson.jsonSchema.annotations.{ - JsonSchemaInject, - JsonSchemaInt, - JsonSchemaString, - JsonSchemaTitle -} -import edu.uci.ics.amber.core.tuple.{AttributeType, Schema} -import edu.uci.ics.amber.core.workflow.{InputPort, OutputPort, PortIdentity} -import edu.uci.ics.amber.operator.PythonOperatorDescriptor -import edu.uci.ics.amber.operator.metadata.annotations.{ - AutofillAttributeName, - CommonOpDescAnnotation, - HideAnnotation -} -import edu.uci.ics.amber.operator.metadata.{OperatorGroupConstants, OperatorInfo} - -class SklearnRFTrainingClassifierOpDesc extends PythonOperatorDescriptor { - - @JsonSchemaTitle("Target Attribute") - @JsonPropertyDescription("Attribute in your dataset corresponding to target.") - @JsonProperty(required = true) - @AutofillAttributeName - var target: String = _ - - @JsonSchemaTitle("Count Vectorizer") - @JsonPropertyDescription("Convert a collection of text documents to a matrix of token counts.") - @JsonProperty(defaultValue = "false") - var countVectorizer: Boolean = false - - @JsonSchemaTitle("Text Attribute") - @JsonPropertyDescription("Attribute in your dataset with text to vectorize.") - @JsonSchemaInject( - strings = Array( - new JsonSchemaString( - path = CommonOpDescAnnotation.autofill, - value = CommonOpDescAnnotation.attributeName - ), - new JsonSchemaString(path = HideAnnotation.hideTarget, value = "countVectorizer"), - new JsonSchemaString(path = HideAnnotation.hideType, value = HideAnnotation.Type.equals), - new JsonSchemaString(path = HideAnnotation.hideExpectedValue, value = "false") - ), - ints = Array( - new JsonSchemaInt(path = CommonOpDescAnnotation.autofillAttributeOnPort, value = 0) - ) - ) - var text: String = _ - - @JsonSchemaTitle("Tfidf Transformer") - @JsonPropertyDescription("Transform a count matrix to a normalized tf or tf-idf representation.") - @JsonProperty(defaultValue = "false") - @JsonSchemaInject( - strings = Array( - new JsonSchemaString(path = HideAnnotation.hideTarget, value = "countVectorizer"), - new JsonSchemaString(path = HideAnnotation.hideType, value = HideAnnotation.Type.equals), - new JsonSchemaString(path = HideAnnotation.hideExpectedValue, value = "false") - ) - ) - val tfidfTransformer: Boolean = false - - @JsonIgnore - def getImportStatements = "from sklearn.ensemble import RandomForestClassifier" - - @JsonIgnore - def getUserFriendlyModelName = "RandomForest Training" - - override def generatePythonCode(): String = - s"""$getImportStatements - |from sklearn.pipeline import make_pipeline - |from sklearn.feature_extraction.text import CountVectorizer, TfidfTransformer - |import numpy as np - |from pytexera import * - |class ProcessTableOperator(UDFTableOperator): - | @overrides - | def process_table(self, table: Table, port: int) -> Iterator[Optional[TableLike]]: - | Y = table["$target"] - | X = table.drop("$target", axis=1) - | X = ${if (countVectorizer) "X['" + text + "']" else "X"} - | model = make_pipeline(${if (countVectorizer) "CountVectorizer()," else ""} ${if (tfidfTransformer) "TfidfTransformer()," else ""} ${getImportStatements.split(" ").last}()).fit(X, Y) - | yield {"model_name" : "$getUserFriendlyModelName", "model" : model} - | - | """.stripMargin - - - override def operatorInfo: OperatorInfo = - OperatorInfo( - getUserFriendlyModelName, - "Sklearn " + getUserFriendlyModelName + " Operator", - OperatorGroupConstants.SKLEARN_GROUP, - inputPorts = List(InputPort(PortIdentity(), "training")), - outputPorts = List(OutputPort(blocking = true)) - ) - - override def getOutputSchemas( - inputSchemas: Map[PortIdentity, Schema] - ): Map[PortIdentity, Schema] = { - Map( - operatorInfo.outputPorts.head.id -> Schema() - .add("model_name", AttributeType.STRING) - .add("model", AttributeType.BINARY) - ) - } -} From 612780dd00aa0d8c603ed4f8d2ce1b517f98151d Mon Sep 17 00:00:00 2001 From: Xinyuan Lin Date: Sun, 6 Jul 2025 18:16:50 -0700 Subject: [PATCH 33/39] fix fmt --- .../operator_images/SklearnRFTraining.png | Bin 81937 -> 0 bytes .../assets/operator_images/SklearnTesting.png | Bin 98115 -> 0 bytes 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 core/gui/src/assets/operator_images/SklearnRFTraining.png delete mode 100644 core/gui/src/assets/operator_images/SklearnTesting.png diff --git a/core/gui/src/assets/operator_images/SklearnRFTraining.png b/core/gui/src/assets/operator_images/SklearnRFTraining.png deleted file mode 100644 index 2ba59197dc8e13c51f4c712ab673bc1b5893e6d6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 81937 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�hHl;lIi1KB-rMXv&j{cYbP_~3Z)>P+ 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 diff --git a/core/gui/src/assets/operator_images/SklearnTesting.png b/core/gui/src/assets/operator_images/SklearnTesting.png deleted file mode 100644 index 622f9a71dd26e795db49ad55aa4d46e262c67b0a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 98115 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ธzEjeiF{5 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