applets: clean shutdown

This commit is contained in:
Sebastien Bourdeauducq 2016-02-08 14:35:37 +01:00
parent 8be0696b39
commit 8844fba4c9
2 changed files with 77 additions and 34 deletions

View File

@ -1,3 +1,4 @@
import logging
import argparse import argparse
import asyncio import asyncio
@ -8,7 +9,13 @@ from artiq.protocols import pyon
from artiq.protocols.pipe_ipc import AsyncioChildComm from artiq.protocols.pipe_ipc import AsyncioChildComm
logger = logging.getLogger(__name__)
class AppletIPCClient(AsyncioChildComm): class AppletIPCClient(AsyncioChildComm):
def set_close_cb(self, close_cb):
self.close_cb = close_cb
def write_pyon(self, obj): def write_pyon(self, obj):
self.write(pyon.encode(obj).encode() + b"\n") self.write(pyon.encode(obj).encode() + b"\n")
@ -17,12 +24,37 @@ class AppletIPCClient(AsyncioChildComm):
return pyon.decode(line.decode()) return pyon.decode(line.decode())
async def embed(self, win_id): async def embed(self, win_id):
# This function is only called when not subscribed to anything,
# so the only normal replies are embed_done and terminate.
self.write_pyon({"action": "embed", self.write_pyon({"action": "embed",
"win_id": win_id}) "win_id": win_id})
reply = await self.read_pyon() reply = await self.read_pyon()
if reply["action"] != "embed_done": if reply["action"] == "terminate":
raise ValueError("Got erroneous reply to embed request", self.close_cb()
reply) elif reply["action"] != "embed_done":
logger.error("unexpected action reply to embed request: %s",
action)
self.close_cb()
async def listen(self):
while True:
obj = await self.read_pyon()
try:
action = obj["action"]
if action == "terminate":
self.close_cb()
return
else:
raise ValueError("unknown action in applet request")
except:
logger.error("error processing applet request",
exc_info=True)
self.close_cb()
def subscribe(self, datasets):
self.write_pyon({"action": "subscribe",
"datasets": datasets})
asyncio.ensure_future(self.listen())
class SimpleApplet: class SimpleApplet:
@ -108,6 +140,7 @@ class SimpleApplet:
# Doing embedding the other way around (using QWindow.setParent in the # Doing embedding the other way around (using QWindow.setParent in the
# applet) breaks resizing. # applet) breaks resizing.
if self.args.mode == "embedded": if self.args.mode == "embedded":
self.ipc.set_close_cb(self.main_widget.close)
win_id = int(self.main_widget.winId()) win_id = int(self.main_widget.winId())
self.loop.run_until_complete(self.ipc.embed(win_id)) self.loop.run_until_complete(self.ipc.embed(win_id))
self.main_widget.show() self.main_widget.show()
@ -155,8 +188,7 @@ class SimpleApplet:
self.loop.run_until_complete(self.subscriber.connect( self.loop.run_until_complete(self.subscriber.connect(
self.args.server_notify, self.args.port_notify)) self.args.server_notify, self.args.port_notify))
elif self.args.mode == "embedded": elif self.args.mode == "embedded":
# TODO self.ipc.subscribe(self.datasets)
pass
else: else:
raise NotImplementedError raise NotImplementedError

View File

@ -7,18 +7,14 @@ from functools import partial
from quamash import QtCore, QtGui, QtWidgets from quamash import QtCore, QtGui, QtWidgets
from pyqtgraph import dockarea from pyqtgraph import dockarea
from artiq.protocols import pyon
from artiq.protocols.pipe_ipc import AsyncioParentComm from artiq.protocols.pipe_ipc import AsyncioParentComm
from artiq.protocols import pyon
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class AppletIPCServer(AsyncioParentComm): class AppletIPCServer(AsyncioParentComm):
def __init__(self, capture_cb):
AsyncioParentComm.__init__(self)
self.capture_cb = capture_cb
def write_pyon(self, obj): def write_pyon(self, obj):
self.write(pyon.encode(obj).encode() + b"\n") self.write(pyon.encode(obj).encode() + b"\n")
@ -26,25 +22,40 @@ class AppletIPCServer(AsyncioParentComm):
line = await self.readline() line = await self.readline()
return pyon.decode(line.decode()) return pyon.decode(line.decode())
async def serve(self): async def serve(self, embed_cb):
try:
while True: while True:
obj = await self.read_pyon() obj = await self.read_pyon()
try: try:
action = obj["action"] action = obj["action"]
if action == "embed": if action == "embed":
self.capture_cb(obj["win_id"]) embed_cb(obj["win_id"])
self.write_pyon({"action": "embed_done"}) self.write_pyon({"action": "embed_done"})
elif action == "subscribe":
print("applet subscribed: ", obj["datasets"])
else: else:
raise ValueError("unknown action in applet request") raise ValueError("unknown action in applet request")
except: except:
logger.warning("error processing applet request", logger.warning("error processing applet request",
exc_info=True) exc_info=True)
self.write_pyon({"action": "error"}) self.write_pyon({"action": "error"})
except asyncio.CancelledError:
pass
except:
logger.error("error processing data from applet, "
"server stopped", exc_info=True)
def start(self, embed_cb):
self.server_task = asyncio.ensure_future(self.serve(embed_cb))
async def stop(self):
self.server_task.cancel()
await asyncio.wait([self.server_task])
class AppletDock(dockarea.Dock): class AppletDock(dockarea.Dock):
def __init__(self, name, command): def __init__(self, name, command):
dockarea.Dock.__init__(self, "applet" + str(id(self)), # XXX dockarea.Dock.__init__(self, "applet" + str(id(self)), # TODO
label="Applet: " + name, label="Applet: " + name,
closable=True) closable=True)
self.setMinimumSize(QtCore.QSize(500, 400)) self.setMinimumSize(QtCore.QSize(500, 400))
@ -56,7 +67,7 @@ class AppletDock(dockarea.Dock):
self.label.setText("Applet: " + name) self.label.setText("Applet: " + name)
async def start(self): async def start(self):
self.ipc = AppletIPCServer(self.capture) self.ipc = AppletIPCServer()
command = self.command.format(python=sys.executable, command = self.command.format(python=sys.executable,
ipc_address=self.ipc.get_address()) ipc_address=self.ipc.get_address())
logger.debug("starting command %s for %s", command, self.applet_name) logger.debug("starting command %s for %s", command, self.applet_name)
@ -65,18 +76,18 @@ class AppletDock(dockarea.Dock):
except: except:
logger.warning("Applet %s failed to start", self.applet_name, logger.warning("Applet %s failed to start", self.applet_name,
exc_info=True) exc_info=True)
asyncio.ensure_future(self.ipc.serve()) self.ipc.start(self.embed)
def capture(self, win_id): def embed(self, win_id):
logger.debug("capturing window 0x%x for %s", win_id, self.applet_name) logger.debug("capturing window 0x%x for %s", win_id, self.applet_name)
captured_window = QtGui.QWindow.fromWinId(win_id) embed_window = QtGui.QWindow.fromWinId(win_id)
captured_widget = QtWidgets.QWidget.createWindowContainer( embed_widget = QtWidgets.QWidget.createWindowContainer(embed_window)
captured_window) self.addWidget(embed_widget)
self.addWidget(captured_widget)
async def terminate(self): async def terminate(self):
if hasattr(self, "process"): if hasattr(self, "ipc"):
# TODO: send IPC termination request await self.ipc.stop()
self.ipc.write_pyon({"action": "terminate"})
try: try:
await asyncio.wait_for(self.ipc.process.wait(), 2.0) await asyncio.wait_for(self.ipc.process.wait(), 2.0)
except: except: