diff --git a/artiq/compiler/transforms/iodelay_estimator.py b/artiq/compiler/transforms/iodelay_estimator.py index 163093bb9..5ba867cc8 100644 --- a/artiq/compiler/transforms/iodelay_estimator.py +++ b/artiq/compiler/transforms/iodelay_estimator.py @@ -24,11 +24,11 @@ class IODelayEstimator(algorithm.Visitor): self.current_goto = None self.current_return = None - def evaluate(self, node, abort): + def evaluate(self, node, abort, context): if isinstance(node, asttyped.NumT): return iodelay.Const(node.n) elif isinstance(node, asttyped.CoerceT): - return self.evaluate(node.value, abort) + return self.evaluate(node.value, abort, context) elif isinstance(node, asttyped.NameT): if self.current_args is None: note = diagnostic.Diagnostic("note", @@ -48,8 +48,8 @@ class IODelayEstimator(algorithm.Visitor): ] abort(notes) elif isinstance(node, asttyped.BinOpT): - lhs = self.evaluate(node.left, abort) - rhs = self.evaluate(node.right, abort) + lhs = self.evaluate(node.left, abort, context) + rhs = self.evaluate(node.right, abort, context) if isinstance(node.op, ast.Add): return lhs + rhs elif isinstance(node.op, ast.Sub): @@ -62,12 +62,14 @@ class IODelayEstimator(algorithm.Visitor): return lhs // rhs else: note = diagnostic.Diagnostic("note", - "this operator is not supported", {}, + "this operator is not supported {context}", + {"context": context}, node.op.loc) abort([note]) else: note = diagnostic.Diagnostic("note", - "this expression is not supported", {}, + "this expression is not supported {context}", + {"context": context}, node.loc) abort([note]) @@ -143,14 +145,14 @@ class IODelayEstimator(algorithm.Visitor): def visit_LambdaT(self, node): self.visit_function(node.args, node.body, node.type.find(), node.loc) - def get_iterable_length(self, node): + def get_iterable_length(self, node, context): def abort(notes): self.abort("for statement cannot be interleaved because " - "trip count is indeterminate", + "iteration count is indeterminate", node.loc, notes) def evaluate(node): - return self.evaluate(node, abort) + return self.evaluate(node, abort, context) if isinstance(node, asttyped.CallT) and types.is_builtin(node.func.type, "range"): range_min, range_max, range_step = iodelay.Const(0), None, iodelay.Const(1) @@ -177,10 +179,11 @@ class IODelayEstimator(algorithm.Visitor): self.current_delay = old_delay else: if self.current_goto is not None: - self.abort("loop trip count is indeterminate because of control flow", + self.abort("loop iteration count is indeterminate because of control flow", self.current_goto.loc) - node.trip_count = self.get_iterable_length(node.iter).fold() + context = "in an iterable used in a for loop that is being interleaved" + node.trip_count = self.get_iterable_length(node.iter, context).fold() node.trip_interval = self.current_delay.fold() self.current_delay = old_delay + node.trip_interval * node.trip_count self.current_goto = old_goto @@ -231,6 +234,10 @@ class IODelayEstimator(algorithm.Visitor): # Interleave failures inside `with` statements are hard failures, # since there's no chance that the code will never actually execute # inside a `with` statement after all. + note = diagnostic.Diagnostic("note", + "while interleaving this 'with parallel:' statement", {}, + node.loc) + error.cause.notes += [note] self.engine.process(error.cause) flow_stmt = None @@ -258,15 +265,17 @@ class IODelayEstimator(algorithm.Visitor): def visit_CallT(self, node): typ = node.func.type.find() def abort(notes): - self.abort("this call cannot be interleaved because " + self.abort("call cannot be interleaved because " "an argument cannot be statically evaluated", node.loc, notes) if types.is_builtin(typ, "delay"): - value = self.evaluate(node.args[0], abort=abort) + value = self.evaluate(node.args[0], abort=abort, + context="as an argument for delay()") call_delay = iodelay.SToMU(value, ref_period=self.ref_period) elif types.is_builtin(typ, "delay_mu"): - value = self.evaluate(node.args[0], abort=abort) + value = self.evaluate(node.args[0], abort=abort, + context="as an argument for delay_mu()") call_delay = value elif not types.is_builtin(typ): if types.is_function(typ): @@ -297,7 +306,12 @@ class IODelayEstimator(algorithm.Visitor): args[arg_name] = arg_node free_vars = delay.duration.free_vars() - node.arg_exprs = { arg: self.evaluate(args[arg], abort=abort) for arg in free_vars } + node.arg_exprs = { + arg: self.evaluate(args[arg], abort=abort, + context="in the expression for argument '{}' " + "that affects I/O delay".format(arg)) + for arg in free_vars + } call_delay = delay.duration.fold(node.arg_exprs) else: assert False diff --git a/artiq/coredevice/core.py b/artiq/coredevice/core.py index 0cf49b27c..3705e5155 100644 --- a/artiq/coredevice/core.py +++ b/artiq/coredevice/core.py @@ -19,7 +19,7 @@ from artiq.coredevice import exceptions def _render_diagnostic(diagnostic, colored): def shorten_path(path): return path.replace(artiq_dir, "") - lines = [shorten_path(path) for path in diagnostic.render(colored)] + lines = [shorten_path(path) for path in diagnostic.render(colored=colored)] return "\n".join(lines) class _DiagnosticEngine(diagnostic.Engine): diff --git a/artiq/gui/console.py b/artiq/gui/console.py index 99fba1fd6..0ec286d55 100644 --- a/artiq/gui/console.py +++ b/artiq/gui/console.py @@ -17,7 +17,7 @@ The following functions are available: class ConsoleDock(dockarea.Dock): def __init__(self, dataset_sub, dataset_ctl): dockarea.Dock.__init__(self, "Console") - self.setMinimumSize(QtCore.QSize(850, 300)) + self.setMinimumSize(QtCore.QSize(720, 300)) self.dataset_sub = dataset_sub self.dataset_ctl = dataset_ctl ns = { diff --git a/artiq/gui/experiments.py b/artiq/gui/experiments.py index 9412404b1..a90a06a4f 100644 --- a/artiq/gui/experiments.py +++ b/artiq/gui/experiments.py @@ -240,7 +240,7 @@ class _ArgumentEditor(QtGui.QTreeWidget): class _ExperimentDock(dockarea.Dock): def __init__(self, manager, expurl): dockarea.Dock.__init__(self, "Exp: " + expurl, closable=True) - self.setMinimumSize(QtCore.QSize(1100, 700)) + self.setMinimumSize(QtCore.QSize(740, 470)) self.layout.setSpacing(5) self.layout.setContentsMargins(5, 5, 5, 5) @@ -488,7 +488,9 @@ class ExperimentManager: def open_experiment(self, expurl): if expurl in self.open_experiments: - return self.open_experiments[expurl] + dock = self.open_experiments[expurl] + self.dock_area.floatDock(dock) + return dock dock = _ExperimentDock(self, expurl) self.open_experiments[expurl] = dock self.dock_area.floatDock(dock) diff --git a/artiq/gui/log.py b/artiq/gui/log.py index 4e068f623..474d040c9 100644 --- a/artiq/gui/log.py +++ b/artiq/gui/log.py @@ -147,7 +147,7 @@ class _LogFilterProxyModel(QSortFilterProxyModel): class _LogDock(dockarea.Dock): def __init__(self, manager, name, log_sub): dockarea.Dock.__init__(self, name, label="Log") - self.setMinimumSize(QtCore.QSize(850, 450)) + self.setMinimumSize(QtCore.QSize(720, 250)) grid = LayoutWidget() self.addWidget(grid) diff --git a/artiq/gui/schedule.py b/artiq/gui/schedule.py index 466b69098..1bac023c8 100644 --- a/artiq/gui/schedule.py +++ b/artiq/gui/schedule.py @@ -58,7 +58,7 @@ class Model(DictSyncModel): class ScheduleDock(dockarea.Dock): def __init__(self, status_bar, schedule_ctl, schedule_sub): dockarea.Dock.__init__(self, "Schedule") - self.setMinimumSize(QtCore.QSize(850, 300)) + self.setMinimumSize(QtCore.QSize(740, 200)) self.status_bar = status_bar self.schedule_ctl = schedule_ctl diff --git a/artiq/master/worker.py b/artiq/master/worker.py index c0c5c95a1..fb1752aed 100644 --- a/artiq/master/worker.py +++ b/artiq/master/worker.py @@ -42,7 +42,7 @@ def log_worker_exception(): class Worker: - def __init__(self, handlers=dict(), send_timeout=0.5): + def __init__(self, handlers=dict(), send_timeout=1.0): self.handlers = handlers self.send_timeout = send_timeout diff --git a/artiq/protocols/asyncio_server.py b/artiq/protocols/asyncio_server.py index a2425137f..70b6eb194 100644 --- a/artiq/protocols/asyncio_server.py +++ b/artiq/protocols/asyncio_server.py @@ -7,7 +7,6 @@ class AsyncioServer: Users of this class must derive from it and define the ``_handle_connection_cr`` method and coroutine. - """ def __init__(self): self._client_tasks = set() @@ -23,15 +22,12 @@ class AsyncioServer: :param host: Bind address of the server (see ``asyncio.start_server`` from the Python standard library). :param port: TCP port to bind to. - """ self.server = await asyncio.start_server(self._handle_connection, host, port) async def stop(self): - """Stops the server. - - """ + """Stops the server.""" wait_for = copy(self._client_tasks) for task in self._client_tasks: task.cancel() @@ -48,6 +44,6 @@ class AsyncioServer: self._client_tasks.remove(task) def _handle_connection(self, reader, writer): - task = asyncio.Task(self._handle_connection_cr(reader, writer)) + task = asyncio.ensure_future(self._handle_connection_cr(reader, writer)) self._client_tasks.add(task) task.add_done_callback(self._client_done) diff --git a/artiq/protocols/logging.py b/artiq/protocols/logging.py index b6ddb9bb6..0c600e288 100644 --- a/artiq/protocols/logging.py +++ b/artiq/protocols/logging.py @@ -78,7 +78,7 @@ class LogParser: entry = (await stream.readline()) if not entry: break - self.line_input(entry[:-1].decode()) + self.line_input(entry.decode().rstrip("\r\n")) except: logger.debug("exception in log forwarding", exc_info=True) break @@ -125,6 +125,9 @@ class Server(AsyncioServer): return source, remainder = linesplit parser.line_input(remainder) + except (ConnectionResetError, ConnectionAbortedError, BrokenPipeError): + # May happens on Windows when client disconnects + pass finally: writer.close() diff --git a/artiq/protocols/pc_rpc.py b/artiq/protocols/pc_rpc.py index 6d9feadb5..d1de343a9 100644 --- a/artiq/protocols/pc_rpc.py +++ b/artiq/protocols/pc_rpc.py @@ -502,6 +502,9 @@ class Server(_AsyncioServer): "message": traceback.format_exc()} line = pyon.encode(obj) + "\n" writer.write(line.encode()) + except (ConnectionResetError, ConnectionAbortedError, BrokenPipeError): + # May happens on Windows when client disconnects + pass finally: writer.close() diff --git a/artiq/protocols/pipe_ipc.py b/artiq/protocols/pipe_ipc.py index 910fe93c4..2955bcaf4 100644 --- a/artiq/protocols/pipe_ipc.py +++ b/artiq/protocols/pipe_ipc.py @@ -3,6 +3,9 @@ import asyncio from asyncio.streams import FlowControlMixin +__all__ = ["AsyncioParentComm", "AsyncioChildComm", "ChildComm"] + + class _BaseIO: def write(self, data): self.writer.write(data) @@ -92,10 +95,78 @@ if os.name != "nt": else: # windows - class AsyncioParentComm(_BaseIO): + import itertools + + + _pipe_count = itertools.count() + + + class AsyncioParentComm: + """Requires ProactorEventLoop""" + def __init__(self): + # We cannot use anonymous pipes on Windows, because we do not know + # in advance if the child process wants a handle open in overlapped + # mode or not. + self.address = "\\\\.\\pipe\\artiq-{}-{}".format(os.getpid(), + next(_pipe_count)) + self.server = None + self.ready = asyncio.Event() + self.write_buffer = b"" + + def get_address(self): + return self.address + async def _autoclose(self): await self.process.wait() - self.writer.close() + if self.server is not None: + self.server[0].close() + self.server = None + if self.ready.is_set(): + self.writer.close() + + async def create_subprocess(self, *args, **kwargs): + loop = asyncio.get_event_loop() + + def factory(): + reader = asyncio.StreamReader(loop=loop) + protocol = asyncio.StreamReaderProtocol(reader, + self._child_connected, + loop=loop) + return protocol + self.server = await loop.start_serving_pipe( + factory, self.address) + + self.process = await asyncio.create_subprocess_exec( + *args, **kwargs) + asyncio.ensure_future(self._autoclose()) + + def _child_connected(self, reader, writer): + self.server[0].close() + self.server = None + self.reader = reader + self.writer = writer + if self.write_buffer: + self.writer.write(self.write_buffer) + self.write_buffer = b"" + self.ready.set() + + def write(self, data): + if self.ready.is_set(): + self.writer.write(data) + else: + self.write_buffer += data + + async def drain(self): + await self.ready.wait() + await self.writer.drain() + + async def readline(self): + await self.ready.wait() + return await self.reader.readline() + + async def read(self, n): + await self.ready.wait() + return await self.reader.read(n) class AsyncioChildComm(_BaseIO): @@ -109,9 +180,26 @@ else: # windows reader_protocol = asyncio.StreamReaderProtocol( self.reader, loop=loop) transport, _ = await loop.create_pipe_connection( - self.address, lambda: reader_protocol) + lambda: reader_protocol, self.address) self.writer = asyncio.StreamWriter(transport, reader_protocol, self.reader, loop) + def close(self): + self.writer.close() + + class ChildComm: - pass + def __init__(self, address): + self.f = open(address, "a+b", 0) + + def read(self, n): + return self.f.read(n) + + def readline(self): + return self.f.readline() + + def write(self, data): + return self.f.write(data) + + def close(self): + self.f.close() diff --git a/artiq/protocols/pyon.py b/artiq/protocols/pyon.py index 8c4afbbdc..238ff2198 100644 --- a/artiq/protocols/pyon.py +++ b/artiq/protocols/pyon.py @@ -74,7 +74,10 @@ class _Encoder: def encode_str(self, x): # Do not use repr() for JSON compatibility. - tt = {ord("\""): "\\\"", ord("\\"): "\\\\", ord("\n"): "\\n"} + tt = { + ord("\""): "\\\"", ord("\\"): "\\\\", + ord("\n"): "\\n", ord("\r"): "\\r" + } return "\"" + x.translate(tt) + "\"" def encode_bytes(self, x): diff --git a/artiq/protocols/sync_struct.py b/artiq/protocols/sync_struct.py index c80555837..039fb2ac4 100644 --- a/artiq/protocols/sync_struct.py +++ b/artiq/protocols/sync_struct.py @@ -235,7 +235,7 @@ class Publisher(AsyncioServer): await writer.drain() finally: self._recipients[notifier_name].remove(queue) - except (ConnectionResetError, BrokenPipeError): + except (ConnectionResetError, ConnectionAbortedError, BrokenPipeError): # subscribers disconnecting are a normal occurence pass finally: diff --git a/artiq/test/lit/iodelay/error_argument.py b/artiq/test/lit/iodelay/error_argument.py new file mode 100644 index 000000000..f92dd559d --- /dev/null +++ b/artiq/test/lit/iodelay/error_argument.py @@ -0,0 +1,12 @@ +# RUN: %python -m artiq.compiler.testbench.signature +diag +delay %s >%t +# RUN: OutputCheck %s --file-to-check=%t + +def f(x): + delay_mu(x) + +x = 1 + +def g(): + # CHECK-L: ${LINE:+2}: error: call cannot be interleaved because an argument cannot be statically evaluated + # CHECK-L: ${LINE:+1}: note: this expression is not supported in the expression for argument 'x' that affects I/O delay + f(x if True else x) diff --git a/artiq/test/lit/iodelay/error_arith.py b/artiq/test/lit/iodelay/error_arith.py index 54e30aa24..6a4c9f0c1 100644 --- a/artiq/test/lit/iodelay/error_arith.py +++ b/artiq/test/lit/iodelay/error_arith.py @@ -3,19 +3,19 @@ def f(a): b = 1.0 - # CHECK-L: ${LINE:+3}: error: this call cannot be interleaved + # CHECK-L: ${LINE:+3}: error: call cannot be interleaved # CHECK-L: ${LINE:+2}: note: this variable is not an argument of the innermost function # CHECK-L: ${LINE:-4}: note: only these arguments are in scope of analysis delay(b) def g(): - # CHECK-L: ${LINE:+2}: error: this call cannot be interleaved - # CHECK-L: ${LINE:+1}: note: this operator is not supported + # CHECK-L: ${LINE:+2}: error: call cannot be interleaved + # CHECK-L: ${LINE:+1}: note: this operator is not supported as an argument for delay() delay(2.0**2) def h(): - # CHECK-L: ${LINE:+2}: error: this call cannot be interleaved - # CHECK-L: ${LINE:+1}: note: this expression is not supported + # CHECK-L: ${LINE:+2}: error: call cannot be interleaved + # CHECK-L: ${LINE:+1}: note: this expression is not supported as an argument for delay_mu() delay_mu(1 if False else 2) f(1) diff --git a/artiq/test/lit/iodelay/error_builtinfn.py b/artiq/test/lit/iodelay/error_builtinfn.py index ad44c972c..b1b89a78f 100644 --- a/artiq/test/lit/iodelay/error_builtinfn.py +++ b/artiq/test/lit/iodelay/error_builtinfn.py @@ -3,12 +3,12 @@ def f(): x = 1 - # CHECK-L: ${LINE:+1}: error: this call cannot be interleaved because an argument cannot be statically evaluated + # CHECK-L: ${LINE:+1}: error: call cannot be interleaved because an argument cannot be statically evaluated delay_mu(x) def g(): x = 1.0 - # CHECK-L: ${LINE:+1}: error: this call cannot be interleaved + # CHECK-L: ${LINE:+1}: error: call cannot be interleaved delay(x) diff --git a/artiq/test/lit/iodelay/error_call_nested.py b/artiq/test/lit/iodelay/error_call_nested.py index b283c0917..2c12af9bd 100644 --- a/artiq/test/lit/iodelay/error_call_nested.py +++ b/artiq/test/lit/iodelay/error_call_nested.py @@ -2,7 +2,7 @@ # RUN: OutputCheck %s --file-to-check=%t def f(): - # CHECK-L: ${LINE:+1}: error: this call cannot be interleaved + # CHECK-L: ${LINE:+1}: error: call cannot be interleaved delay(1.0**2) def g(): diff --git a/artiq/test/lit/iodelay/error_call_subst.py b/artiq/test/lit/iodelay/error_call_subst.py index 62c6bb29a..0d1ba0843 100644 --- a/artiq/test/lit/iodelay/error_call_subst.py +++ b/artiq/test/lit/iodelay/error_call_subst.py @@ -9,5 +9,5 @@ def pulse(len): def f(): a = 100 - # CHECK-L: ${LINE:+1}: error: this call cannot be interleaved + # CHECK-L: ${LINE:+1}: error: call cannot be interleaved pulse(a) diff --git a/artiq/test/lit/iodelay/error_for.py b/artiq/test/lit/iodelay/error_for.py index 5c5b10ffc..aae5d47f3 100644 --- a/artiq/test/lit/iodelay/error_for.py +++ b/artiq/test/lit/iodelay/error_for.py @@ -3,7 +3,7 @@ def f(): r = range(10) - # CHECK-L: ${LINE:+2}: error: for statement cannot be interleaved because trip count is indeterminate + # CHECK-L: ${LINE:+2}: error: for statement cannot be interleaved because iteration count is indeterminate # CHECK-L: ${LINE:+1}: note: this value is not a constant range literal for _ in r: delay_mu(1) diff --git a/artiq/test/lit/iodelay/error_goto.py b/artiq/test/lit/iodelay/error_goto.py index 02686cd86..714db7610 100644 --- a/artiq/test/lit/iodelay/error_goto.py +++ b/artiq/test/lit/iodelay/error_goto.py @@ -4,11 +4,11 @@ def f(): for _ in range(10): delay_mu(10) - # CHECK-L: ${LINE:+1}: error: loop trip count is indeterminate because of control flow + # CHECK-L: ${LINE:+1}: error: loop iteration count is indeterminate because of control flow break def g(): for _ in range(10): delay_mu(10) - # CHECK-L: ${LINE:+1}: error: loop trip count is indeterminate because of control flow + # CHECK-L: ${LINE:+1}: error: loop iteration count is indeterminate because of control flow continue diff --git a/artiq/test/lit/iodelay/error_iterable.py b/artiq/test/lit/iodelay/error_iterable.py new file mode 100644 index 000000000..6429236db --- /dev/null +++ b/artiq/test/lit/iodelay/error_iterable.py @@ -0,0 +1,10 @@ +# RUN: %python -m artiq.compiler.testbench.signature +diag +delay %s >%t +# RUN: OutputCheck %s --file-to-check=%t + +x = 1 + +def f(): + # CHECK-L: ${LINE:+2}: error: for statement cannot be interleaved because iteration count is indeterminate + # CHECK-L: ${LINE:+1}: note: this expression is not supported in an iterable used in a for loop that is being interleaved + for _ in range(x if True else x): + delay_mu(10) diff --git a/artiq/test/serialization.py b/artiq/test/serialization.py index 7bcbed5eb..88cf9b18e 100644 --- a/artiq/test/serialization.py +++ b/artiq/test/serialization.py @@ -26,7 +26,7 @@ class PYON(unittest.TestCase): _json_test_object = { "a": "b", "x": [1, 2, {}], - "foo\nbaz\\qux\"": ["bar", 1.2, {"x": "y"}], + "foo\nbaz\\qux\"\r2": ["bar", 1.2, {"x": "y"}], "bar": [True, False, None] } diff --git a/conda/artiq/meta.yaml b/conda/artiq/meta.yaml index d4f447496..5ef780b86 100644 --- a/conda/artiq/meta.yaml +++ b/conda/artiq/meta.yaml @@ -54,7 +54,7 @@ requirements: - pyqtgraph - pygit2 - aiohttp - - binutils-or1k-linux + - binutils-or1k-linux # [linux] - pythonparser - levenshtein