Skip to content

Commit 4eaa7df

Browse files
authored
FCI Stream API (#1758)
* Added more detail when recursive data is found in FOBS * Stream API * Changed API to use future and message * Made object_cb required * Revamped to new StreamCell API * Added handling for the special case when first chunk arrives late * Increased the reassembly buffer size * Added tools * Fixed several dead-lock errors * Added unit test for streaming and utility to send files * Addressed issues in the PR
1 parent 8b10e7c commit 4eaa7df

29 files changed

+1946
-142
lines changed

nvflare/fuel/f3/cellnet/cell.py

Lines changed: 24 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@
3737
ServiceUnavailable,
3838
)
3939
from nvflare.fuel.f3.cellnet.fqcn import FQCN, FqcnInfo, same_family
40-
from nvflare.fuel.f3.cellnet.utils import decode_payload, encode_payload, format_log_message, make_reply, new_message
40+
from nvflare.fuel.f3.cellnet.registry import Callback, Registry
41+
from nvflare.fuel.f3.cellnet.utils import decode_payload, encode_payload, format_log_message, make_reply
4142
from nvflare.fuel.f3.comm_config import CommConfigurator
4243
from nvflare.fuel.f3.communicator import Communicator, MessageReceiver
4344
from nvflare.fuel.f3.connection import Connection
@@ -86,7 +87,7 @@ def to_dict(self):
8687
@staticmethod
8788
def from_dict(d: dict):
8889
msg_dict = d.get("message")
89-
msg = new_message(headers=msg_dict.get("headers"), payload=msg_dict.get("payload"))
90+
msg = Message(headers=msg_dict.get("headers"), payload=msg_dict.get("payload"))
9091
return TargetMessage(target=d.get("target"), channel=d.get("channel"), topic=d.get("topic"), message=msg)
9192

9293

@@ -110,46 +111,6 @@ def get_fqcn(self):
110111
return self.info.fqcn
111112

112113

113-
class _CB:
114-
def __init__(self, cb, args, kwargs):
115-
self.cb = cb
116-
self.args = args
117-
self.kwargs = kwargs
118-
119-
120-
class _Registry:
121-
def __init__(self):
122-
self.reg = {} # channel/topic => _CB
123-
124-
@staticmethod
125-
def _item_key(channel: str, topic: str) -> str:
126-
return f"{channel}:{topic}"
127-
128-
def set(self, channel: str, topic: str, items):
129-
key = self._item_key(channel, topic)
130-
self.reg[key] = items
131-
132-
def append(self, channel: str, topic: str, items):
133-
key = self._item_key(channel, topic)
134-
item_list = self.reg.get(key)
135-
if not item_list:
136-
item_list = []
137-
self.reg[key] = item_list
138-
item_list.append(items)
139-
140-
def find(self, channel: str, topic: str):
141-
items = self.reg.get(self._item_key(channel, topic))
142-
if not items:
143-
# try topic * in channel
144-
items = self.reg.get(self._item_key(channel, "*"))
145-
146-
if not items:
147-
# try topic * in channel *
148-
items = self.reg.get(self._item_key("*", "*"))
149-
150-
return items
151-
152-
153114
class _Waiter(threading.Event):
154115
def __init__(self, targets: List[str]):
155116
super().__init__()
@@ -215,7 +176,7 @@ def send(self):
215176
f"{self.cell.get_fqcn()}: bulk sender {self.target} sending bulk size {len(messages_to_send)}"
216177
)
217178
tms = [m.to_dict() for m in messages_to_send]
218-
bulk_msg = new_message(payload=tms)
179+
bulk_msg = Message(None, tms)
219180
send_errs = self.cell.fire_and_forget(
220181
channel=_CHANNEL, topic=_TOPIC_BULK, targets=[self.target], message=bulk_msg
221182
)
@@ -380,12 +341,12 @@ def __init__(
380341

381342
self.communicator.register_message_receiver(app_id=self.APP_ID, receiver=self)
382343
self.communicator.register_monitor(monitor=self)
383-
self.req_reg = _Registry()
384-
self.in_req_filter_reg = _Registry() # for request received
385-
self.out_reply_filter_reg = _Registry() # for reply going out
386-
self.out_req_filter_reg = _Registry() # for request sent
387-
self.in_reply_filter_reg = _Registry() # for reply received
388-
self.error_handler_reg = _Registry()
344+
self.req_reg = Registry()
345+
self.in_req_filter_reg = Registry() # for request received
346+
self.out_reply_filter_reg = Registry() # for reply going out
347+
self.out_req_filter_reg = Registry() # for request sent
348+
self.in_reply_filter_reg = Registry() # for reply received
349+
self.error_handler_reg = Registry()
389350
self.cell_connected_cb = None
390351
self.cell_connected_cb_args = None
391352
self.cell_connected_cb_kwargs = None
@@ -866,7 +827,7 @@ def stop(self):
866827
channel=_CHANNEL,
867828
topic=_TOPIC_BYE,
868829
targets=targets,
869-
request=new_message(),
830+
request=Message(),
870831
timeout=0.5,
871832
optional=True,
872833
)
@@ -905,39 +866,39 @@ def register_request_cb(self, channel: str, topic: str, cb, *args, **kwargs):
905866
"""
906867
if not callable(cb):
907868
raise ValueError(f"specified request_cb {type(cb)} is not callable")
908-
self.req_reg.set(channel, topic, _CB(cb, args, kwargs))
869+
self.req_reg.set(channel, topic, Callback(cb, args, kwargs))
909870

910871
def add_incoming_request_filter(self, channel: str, topic: str, cb, *args, **kwargs):
911872
if not callable(cb):
912873
raise ValueError(f"specified incoming_request_filter {type(cb)} is not callable")
913-
self.in_req_filter_reg.append(channel, topic, _CB(cb, args, kwargs))
874+
self.in_req_filter_reg.append(channel, topic, Callback(cb, args, kwargs))
914875

915876
def add_outgoing_reply_filter(self, channel: str, topic: str, cb, *args, **kwargs):
916877
if not callable(cb):
917878
raise ValueError(f"specified outgoing_reply_filter {type(cb)} is not callable")
918-
self.out_reply_filter_reg.append(channel, topic, _CB(cb, args, kwargs))
879+
self.out_reply_filter_reg.append(channel, topic, Callback(cb, args, kwargs))
919880

920881
def add_outgoing_request_filter(self, channel: str, topic: str, cb, *args, **kwargs):
921882
if not callable(cb):
922883
raise ValueError(f"specified outgoing_request_filter {type(cb)} is not callable")
923-
self.out_req_filter_reg.append(channel, topic, _CB(cb, args, kwargs))
884+
self.out_req_filter_reg.append(channel, topic, Callback(cb, args, kwargs))
924885

925886
def add_incoming_reply_filter(self, channel: str, topic: str, cb, *args, **kwargs):
926887
if not callable(cb):
927888
raise ValueError(f"specified incoming_reply_filter {type(cb)} is not callable")
928-
self.in_reply_filter_reg.append(channel, topic, _CB(cb, args, kwargs))
889+
self.in_reply_filter_reg.append(channel, topic, Callback(cb, args, kwargs))
929890

930891
def add_error_handler(self, channel: str, topic: str, cb, *args, **kwargs):
931892
if not callable(cb):
932893
raise ValueError(f"specified error_handler {type(cb)} is not callable")
933-
self.error_handler_reg.set(channel, topic, _CB(cb, args, kwargs))
894+
self.error_handler_reg.set(channel, topic, Callback(cb, args, kwargs))
934895

935896
def _filter_outgoing_request(self, channel: str, topic: str, request: Message) -> Union[None, Message]:
936897
cbs = self.out_req_filter_reg.find(channel, topic)
937898
if not cbs:
938899
return None
939900
for _cb in cbs:
940-
assert isinstance(_cb, _CB)
901+
assert isinstance(_cb, Callback)
941902
reply = self._try_cb(request, _cb.cb, *_cb.args, **_cb.kwargs)
942903
if reply:
943904
return reply
@@ -1112,7 +1073,7 @@ def _send_target_messages(
11121073
self.logger.debug(f"{self.my_info.fqcn}: invoking outgoing request filters")
11131074
assert isinstance(req_filters, list)
11141075
for f in req_filters:
1115-
assert isinstance(f, _CB)
1076+
assert isinstance(f, Callback)
11161077
r = self._try_cb(req, f.cb, *f.args, **f.kwargs)
11171078
if r:
11181079
send_errs[t] = ReturnCode.FILTER_ERROR
@@ -1343,7 +1304,7 @@ def _peer_goodbye(self, request: Message):
13431304
self.logger.debug(f"{self.my_info.fqcn}: agent for {peer_ep.name} is already gone")
13441305

13451306
# ack back
1346-
return new_message()
1307+
return Message()
13471308

13481309
def _receive_bulk_message(self, request: Message):
13491310
target_msgs = request.payload
@@ -1477,20 +1438,19 @@ def _process_request(self, origin: str, message: Message) -> Union[None, Message
14771438
self.logger.debug(f"{self.my_info.fqcn}: invoking incoming request filters")
14781439
assert isinstance(req_filters, list)
14791440
for f in req_filters:
1480-
assert isinstance(f, _CB)
1441+
assert isinstance(f, Callback)
14811442
reply = self._try_cb(message, f.cb, *f.args, **f.kwargs)
14821443
if reply:
14831444
return reply
14841445

1485-
assert isinstance(_cb, _CB)
1446+
assert isinstance(_cb, Callback)
14861447
self.logger.debug(f"{self.my_info.fqcn}: calling registered request CB")
14871448
cb_start = time.perf_counter()
14881449
reply = self._try_cb(message, _cb.cb, *_cb.args, **_cb.kwargs)
14891450
cb_end = time.perf_counter()
14901451
self.req_cb_stats_pool.record_value(category=self._stats_category(message), value=cb_end - cb_start)
14911452
if not reply:
14921453
# the CB doesn't have anything to reply
1493-
self.logger.debug("no reply is returned from the CB")
14941454
return None
14951455

14961456
if not isinstance(reply, Message):
@@ -1630,7 +1590,7 @@ def _process_reply(self, origin: str, message: Message, msg_type: str):
16301590
self.logger.debug(f"{self.my_info.fqcn}: invoking incoming reply filters")
16311591
assert isinstance(reply_filters, list)
16321592
for f in reply_filters:
1633-
assert isinstance(f, _CB)
1593+
assert isinstance(f, Callback)
16341594
self._try_cb(message, f.cb, *f.args, **f.kwargs)
16351595

16361596
for rid in req_ids:
@@ -1808,7 +1768,6 @@ def _process_received_msg(self, endpoint: Endpoint, connection: Connection, mess
18081768
reply = self._process_request(origin, message)
18091769

18101770
if not reply:
1811-
self.logger.debug(f"{self.my_info.fqcn}: don't send response - nothing to send")
18121771
self.received_msg_counter_pool.increment(
18131772
category=self._stats_category(message), counter_name=_CounterName.REPLY_NONE
18141773
)
@@ -1863,7 +1822,7 @@ def _process_received_msg(self, endpoint: Endpoint, connection: Connection, mess
18631822
self.logger.debug(f"{self.my_info.fqcn}: invoking outgoing reply filters")
18641823
assert isinstance(reply_filters, list)
18651824
for f in reply_filters:
1866-
assert isinstance(f, _CB)
1825+
assert isinstance(f, Callback)
18671826
r = self._try_cb(reply, f.cb, *f.args, **f.kwargs)
18681827
if r:
18691828
reply = r

0 commit comments

Comments
 (0)