def _on_response(self, response: Response) -> None: # called from tornado thread message = json_encode(response) try: self.write_message(message) except WebSocketError: # The websocket is dead. If the response was a Delta or Update, then # unsubscribe so the local controller doesn't keep on trying to # respond if isinstance(response, (Delta, Update)): # Websocket is dead so we can clear the subscription key. # Subsequent updates may come in before the unsubscribe, but # ignore them as we can't do anything about it mri = self._id_to_mri.pop(response.id, None) if mri: log.info("WebSocket Error: unsubscribing from stale handle") unsubscribe = Unsubscribe(response.id) unsubscribe.set_callback(self.on_response) if self._registrar: self._registrar.report( builtin.infos.RequestInfo(unsubscribe, mri) ) finally: assert self._queue, "No queue" cothread.Callback(self._queue.put, None)
def onLastDisconnect(self, pv: SharedPV) -> None: assert self.pv, "onFirstConnect not called yet" # No-one listening, unsubscribe with self._lock: self.pv.close() self.pv = None self.value = None request = Unsubscribe() request.set_callback(self.handle) self.controller.handle_request(request).get(timeout=1)
def update_part_exportable(self, response): # Get a child context to check if we have a config field child = self.child_controller.block_view() spawned = [] if response.value: new_fields = response.value else: new_fields = [] # Remove any existing subscription that is not in the new fields for subscribe in self.config_subscriptions.values(): attr_name = subscribe.path[-2] if attr_name not in new_fields: unsubscribe = Unsubscribe(subscribe.id, subscribe.callback) spawned.append( self.child_controller.handle_request(unsubscribe)) self.port_infos.pop(attr_name, None) # Add a subscription to any new field existing_fields = set(s.path[-2] for s in self.config_subscriptions.values()) for field in set(new_fields) - existing_fields: attr = getattr(child, field) if isinstance(attr, Attribute): for tag in attr.meta.tags: match = port_tag_re.match(tag) if match: d, type, extra = match.groups() self.port_infos[field] = PortInfo(name=field, value=attr.value, direction=d, type=type, extra=extra) if isinstance(attr, Attribute) and config() in attr.meta.tags: if self.config_subscriptions: new_id = max(self.config_subscriptions) + 1 else: new_id = 1 subscribe = Subscribe(id=new_id, path=[self.params.mri, field, "value"], callback=self.update_part_modified) self.config_subscriptions[new_id] = subscribe # Signal that any change we get is a difference if field not in self.saved_structure: self.saved_structure[field] = None spawned.append(self.child_controller.handle_request(subscribe)) # Wait for the first update to come in for s in spawned: s.wait() # Put data on the queue, so if spawns are handled out of order we # still get the most up to date data port_infos = [ self.port_infos[f] for f in new_fields if f in self.port_infos ] self.exportable_update_queue.put((new_fields, port_infos)) self.spawn(self._update_part_exportable).wait()
def on_response(self, response, write_message): # called from tornado thread message = json_encode(response) try: write_message(message) except WebSocketError: if isinstance(response, (Delta, Update)): request = self._subscription_keys[response.id] unsubscribe = Unsubscribe(request.id) controller = self.process.get_controller(request.path[0]) controller.handle_request(unsubscribe)
def _get_current_part_fields(self): # Clear out the current subscriptions for subscription in self._subscriptions: controller = self.process.get_controller(subscription.path[0]) unsubscribe = Unsubscribe(subscription.id) unsubscribe.set_callback(subscription.callback) controller.handle_request(unsubscribe) self._subscriptions = [] # Find the mris of parts mris = {} invisible = set() for part_name, mri, visible in zip(self.layout.value.name, self.layout.value.mri, self.layout.value.visible): if visible: mris[part_name] = mri else: invisible.add(part_name) # Add fields from parts that aren't invisible for part_name, part in self.parts.items(): if part_name not in invisible: for data in self.field_registry.fields.get(part, []): yield data # Add exported fields from visible parts for source, export_name in self.exports.value.rows(): part_name, attr_name = source.rsplit(".", 1) part = self.parts[part_name] # If part is visible, get its mri mri = mris.get(part_name, None) if mri and attr_name in self.part_exportable.get(part, []): if not export_name: export_name = attr_name export, setter = self._make_export_field( mri, attr_name, export_name) yield export_name, export, setter, False
def halt(self, context): unsubscribe = Unsubscribe(callback=self.update_part_exportable) self.child_controller.handle_request(unsubscribe)
def update_part_exportable(self, response: Response) -> None: # Get a child context to check if we have a config field assert self.child_controller, "No child controller" child = self.child_controller.block_view() spawned = [] if isinstance(response, Update): new_fields = response.value assert isinstance(new_fields, Sequence), f"Bad field list {new_fields}" elif isinstance(response, Return): # We got a return with None, so clear out all of the # config_subscriptions new_fields = [] else: self.log.warning("Got unexpected response {response}") return # Remove any existing subscription that is not in the new fields for subscribe in self.config_subscriptions.values(): attr_name = subscribe.path[-2] if attr_name not in new_fields: unsubscribe = Unsubscribe(subscribe.id) unsubscribe.set_callback(subscribe.callback) spawned.append(self.child_controller.handle_request(unsubscribe)) self.port_infos.pop(attr_name, None) # Add a subscription to any new field existing_fields = set(s.path[-2] for s in self.config_subscriptions.values()) for field in set(new_fields) - existing_fields: attr = getattr(child, field) if isinstance(attr, Attribute): # Cache tags here tags = attr.meta.tags # Check if the attribute has any port tags, and store for # when we are asked for LayoutInfo port_info = Port.port_tag_details(tags) info: PortInfo if port_info: is_source, port, extra = port_info if is_source: info = SourcePortInfo( name=field, port=port, connected_value=extra ) else: info = SinkPortInfo( name=field, port=port, disconnected_value=extra, value=attr.value, ) self.port_infos[field] = info # If we are config tagged then subscribe so we can calculate # if we are modified if self._unmanaged_attr(field) and get_config_tag(tags): if self.config_subscriptions: new_id = max(self.config_subscriptions) + 1 else: new_id = 1 subscribe = Subscribe(id=new_id, path=[self.mri, field, "value"]) subscribe.set_callback(self.update_part_modified) self.config_subscriptions[new_id] = subscribe spawned.append(self.child_controller.handle_request(subscribe)) # Wait for the first update to come in for every subscription for s in spawned: s.wait() port_infos = [self.port_infos[f] for f in new_fields if f in self.port_infos] assert self.registrar, "No registrar assigned" self.registrar.report(PartExportableInfo(new_fields, port_infos))
def on_halt(self) -> None: unsubscribe = Unsubscribe() unsubscribe.set_callback(self.update_part_exportable) assert self.child_controller, "No child controller" self.child_controller.handle_request(unsubscribe)
def halt(self, context): super(RunnableChildPart, self).halt(context) unsubscribe = Unsubscribe(callback=self.update_part_configure_args) self.child_controller.handle_request(unsubscribe)
def test_handle_request(self): q = Queue() request = Get(id=41, path=["mri", "myAttribute"]) request.set_callback(q.put) self.o.handle_request(request) response = q.get(timeout=0.1) self.assertIsInstance(response, Return) assert response.id == 41 assert response.value["value"] == "hello_block" self.part.my_attribute.meta.writeable = False request = Put( id=42, path=["mri", "myAttribute"], value="hello_block2", get=True ) request.set_callback(q.put) self.o.handle_request(request) response = q.get(timeout=0.1) self.assertIsInstance(response, Error) # not writeable assert response.id == 42 self.part.my_attribute.meta.writeable = True self.o.handle_request(request) response = q.get(timeout=0.1) self.assertIsInstance(response, Return) assert response.id == 42 assert response.value == "hello_block2" request = Post(id=43, path=["mri", "method"]) request.set_callback(q.put) self.o.handle_request(request) response = q.get(timeout=0.1) self.assertIsInstance(response, Return) assert response.id == 43 assert response.value == "world" # cover the controller._handle_post path for parameters request = Post(id=43, path=["mri", "method"], parameters={"dummy": 1}) request.set_callback(q.put) self.o.handle_request(request) response = q.get(timeout=0.1) self.assertIsInstance(response, Error) assert response.id == 43 assert ( str(response.message) == "Given keys ['dummy'], some of which aren't in allowed keys []" ) request = Subscribe(id=44, path=["mri", "myAttribute"], delta=False) request.set_callback(q.put) self.o.handle_request(request) response = q.get(timeout=0.1) self.assertIsInstance(response, Update) assert response.id == 44 assert response.value["typeid"] == "epics:nt/NTScalar:1.0" assert response.value["value"] == "hello_block2" request = Unsubscribe(id=44) request.set_callback(q.put) self.o.handle_request(request) response = q.get(timeout=0.1) self.assertIsInstance(response, Return) assert response.id == 44
def halt(self): # type: () -> None unsubscribe = Unsubscribe() unsubscribe.set_callback(self.update_part_exportable) self.child_controller.handle_request(unsubscribe)
def halt(self): unsubscribe = Unsubscribe(callback=self.handle_response) self.client_comms.send_to_server(unsubscribe)
def halt(self): # type: () -> None super(RunnableChildPart, self).halt() unsubscribe = Unsubscribe() unsubscribe.set_callback(self.update_part_configure_args) self.child_controller.handle_request(unsubscribe)