示例#1
0
    def _decode(self, observation, start, available_at):
        # This method gets wrapped by AsyncDecode.__call__
        with pyprofile.push(
                'vnc_env.diagnostics.QRCodeMetadataDecoder.qr_code_scanner'):

            encoded = fastzbarlight.qr_code_scanner(observation.tobytes(),
                                                    self.width, self.height)
        if encoded is None:
            # Failed to parse!
            return
        if encoded.startswith(b'v1:'):
            encoded = encoded.decode('utf-8')
            if len(encoded) != len('v1:') + 12 + 12:
                raise error.Error(
                    'Bad length for metadata from enviroment: {}'.format(
                        encoded))

            encoded = encoded[len('v1:'):]
            last_update = int(encoded[:12], 16) / 1000.0
            last_action = int(encoded[12:24], 16) / 1000.

            return {
                # Timestamp on the image
                'now': last_update,
                # When the last probe was received
                'probe_received_at': last_action,
                'processing_start': start,
                'processing_end': time.time(),
                'available_at': available_at,
            }
        else:
            raise error.Error(
                'Bad version string for metadata from environment: {}'.format(
                    encoded))
示例#2
0
文件: build.py 项目: sibeshkar/jiminy
def build(client_id, remotes, runtime=None, start_timeout=None, **kwargs):
    if isinstance(remotes, int):
        remotes = str(remotes)
    elif not isinstance(remotes, str):
        raise error.Error('remotes argument must be a string, got {} which is of type {}'.format(remotes, type(remotes)))

    if re.search('^\d+$', remotes): # an integer, like -r 20
        n = int(remotes)
        return DockerManager(
            runtime=runtime,
            start_timeout=start_timeout,
            reuse=kwargs.get('reuse', False),
            n=n,
        ), n
    elif remotes.startswith('vnc://'):
        return HardcodedAddresses.build(
            remotes,
            start_timeout=start_timeout)
    elif remotes.startswith('http://') or remotes.startswith('https://'):
        if runtime is None:
            raise error.Error('Must provide a runtime. HINT: try creating your env instance via gym.make("flashgames.DuskDrive-v0")')

        manager, n = AllocatorManager.from_remotes(
            client_id,
            remotes,
            runtime_id=runtime.id,
            runtime_tag=runtime.image.split(':')[-1],
            start_timeout=start_timeout,
            api_key=kwargs.get('api_key'),
            use_recorder_ports=kwargs.get('use_recorder_ports', False),
        )
        manager.start()
        return manager, n
    else:
        raise error.Error('Invalid remotes: {!r}. Must be an integer or must start with vnc:// or https://'.format(remotes))
示例#3
0
    def allocate_ports(self, num):
        if self.reuse and self._next_port in self._ports:
            vnc_id = self._ports[self._next_port]
            rewarder_id = self._ports.get(self._next_port + 10000)

            # Reuse an existing docker container if it exists
            if (self._next_port + 10000) not in self._ports:
                raise error.Error(
                    "Port {} was allocated but {} was not. This indicates unexpected state with spun-up VNC docker instances."
                    .format(self._next_port, self._next_port + 1))
            elif vnc_id != rewarder_id:
                raise error.Error(
                    "Port {} is exposed from {} while {} is exposed from {}. Both should come from a single Docker instance running your environment."
                    .format(vnc_id, self._next_port, rewarder_id,
                            self._next_port + 10000))

            base = self._next_port
            self._next_port += 1
            return base, base + 10000, vnc_id
        elif not self.reuse:
            # Otherwise, allocate find the lowest free pair of
            # ports. This doesn't work for the reuse case since on
            # restart we won't remember where we spun up our
            # containers.
            while self._next_port in self._ports or (self._next_port +
                                                     10000) in self._ports:
                self._next_port += 1

        base = self._next_port
        self._next_port += 1

        # And get started!
        return base, base + 10000, None
示例#4
0
 def processExited(self, reason):
     if isinstance(reason.value, twisted.internet.error.ProcessDone):
         out = b''.join(self.out).decode('utf-8')
         match = re.search('offset ([\d.-]+) sec', out)
         if match is not None:
             offset = float(match.group(1))
             self.deferred.callback(offset)
         else:
             self.deferred.errback(
                 error.Error('Could not parse offset: %s', out))
     else:
         err = b''.join(self.err)
         self.deferred.errback(
             error.Error('{} failed with status {}: stderr={!r}'.format(
                 self._cmd, reason.value.exitCode, err)))
示例#5
0
 def allocate(self, handles, initial=False, params={}):
     if len(handles) > self.available_n:
         raise error.Error(
             'Requested {} handles, but only have {} envs'.format(
                 len(handles), self.available_n))
     self.n = len(handles)
     self._handles = handles
示例#6
0
    def _register_vnc(self, address, start_time=None):
        if start_time is None:
            start_time = time.time()

        host, port = host_port(address, default_port=5900)

        while True:
            # In VNC, the server sends bytes upon connection
            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            try:
                sock.connect((host, port))
            except (socket.error, socket.gaierror) as e:
                # ECONNREFUSED: VNC env hasn't come up yet
                # ETIMEDOUT: the packets can't be delivered yet, such as can happen on kubernetes
                # gaierror: can't resolve the address yet, which can also happen on kubernetes
                expected = socket.errno.ECONNREFUSED == e.errno or socket.errno.ETIMEDOUT == e.errno or isinstance(e, socket.gaierror)
                if self.start_timeout is None or not expected:
                    reraise(suffix='while connecting to VNC server {}'.format(address))
                logger.info('VNC server %s did not come up yet (error: %s). Sleeping for 1s.', address, e)
                time.sleep(1)
            else:
                break

            if time.time() - start_time > self.start_timeout:
                raise error.Error('VNC server {} did not come up within {}s'.format(address, self.start_timeout))

        self.sockets[sock] = ('vnc', address)
示例#7
0
    def _register_rewarder(self, address, start_time=None):
        if start_time is None:
            start_time = time.time()

        host, port = host_port(address, default_port=15900)

        while True:
            # In WebSockets, the server sends bytes once we've upgraded the protocol
            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            try:
                sock.connect((host, port))
            except (socket.error, socket.gaierror) as e:
                # ECONNREFUSED: VNC env hasn't come up yet
                # ETIMEDOUT: the packets can't be delivered yet, such as can happen on kubernetes
                # gaierror: can't resolve the address yet, which can also happen on kubernetes
                expected = socket.errno.ECONNREFUSED == e.errno or socket.errno.ETIMEDOUT == e.errno or isinstance(e, socket.gaierror)
                if self.start_timeout is None or not expected:
                    reraise(suffix='while connecting to Rewarder server {}'.format(address))
                logger.info('Rewarder server %s did not come up yet (error: %s). Sleeping for 1s.', address, e)
                time.sleep(1)
            else:
                break

            if time.time() - start_time > self.start_timeout:
                raise error.Error('Rewarder server {} did not come up within {}s'.format(address, self.start_timeout))

        # Send a websocket handshake.
        # https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_WebSocket_servers
        #
        # The port 10003 is an arbitrary port that we don't actually connect to, but needs to be a valid part
        # e.g Host: 127.0.0.1:GARBAGE results in the following error: (invalid port 'GARBAGE' in HTTP Host header '127.0.0.1:GARBAGE')
        sock.send(b'GET / HTTP/1.1\r\nHost: 127.0.0.1:10003\r\nUpgrade: WebSocket\r\nConnection:Upgrade\r\nSec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\nSec-WebSocket-Version: 13\r\nauthorization: ' + utils.basic_auth_encode('boxware').encode('utf-8') + b'\r\nboxware-observer: true\r\n\r\n')
        self.sockets[sock] = ('rewarder', address)
示例#8
0
 def fail(reason):
     reason = error.Error('[{}] Connection failed: {}'.format(
         factory.label, reason.value))
     try:
         d.errback(utils.format_error(reason))
     except defer.AlreadyCalledError:
         pass
示例#9
0
    def _initialize(self):
        if not os.environ.get('DISPLAY') and sys.platform.startswith('linux'):
            raise error.Error("Cannot render with mode='human' with no DISPLAY variable set.")

        import pyglet
        self._window = pyglet.window.Window(width=self._width, height=self._height, visible=True)
        self._window.dispatch_events()
        self.texture = pyglet.image.Texture.create(width=self._width, height=self._height)
示例#10
0
def rewarder_session(which):
    if which is None:
        which = rewarder.RewarderSession

    if isinstance(which, type):
        return which
    else:
        raise error.Error('Invalid RewarderSession driver: {!r}'.format(which))
示例#11
0
 def apply(self, framebuffer_update):
     pyprofile.push('vncdriver.pyglet_screen.apply')
     for rect in framebuffer_update.rectangles:
         if isinstance(rect.encoding,
                       (server_messages.RAWEncoding, server_messages.ZRLEEncoding, server_messages.ZlibEncoding)):
             self.update_rectangle(rect.x, rect.y, rect.width, rect.height, rect.encoding.data)
         else:
             raise error.Error('Unrecognized encoding: {}'.format(rect.encoding))
     pyprofile.pop()
示例#12
0
    def onClose(self, wasClean, code, reason):
        if self._close_message:
            return

        if not self._connected:
            assert not self._connection_result.called
            self._connection_result.errback(error.ConnectionError(reason))
            return

        if not self._closed:
            error_message = 'Lost connection: {} (clean={} code={})'.format(
                reason, wasClean, code)
            reason = error.Error(error_message)
            # TODO: it's not an error if we requested it
            self.factory.record_error(reason)
        else:
            reason = error.Error("We closed the connection: {}".format(reason))

        for request, d in self._requests.values():
            d.errback(reason)
示例#13
0
    def env(self):
        # Called upon instantiation
        if not hasattr(self, '_env_ref'):
            return

        env = self._env_ref()
        if env is None:
            raise error.Error(
                "env has been garbage collected. To keep using WeakUnvectorize, you must keep around a reference to the env object. (HINT: try assigning the env to a variable in your code.)"
            )
        return env
示例#14
0
    def __init__(self, env):
        super(Wrapper, self).__init__(env)
        if not env.metadata.get('runtime.vectorized'):
            if self.autovectorize:
                # Circular dependency :(
                from jiminy import wrappers
                env = wrappers.Vectorize(env)
            else:
                raise error.Error('This wrapper can only wrap vectorized envs (i.e. where env.metadata["runtime.vectorized"] = True), not {}. Set "self.autovectorize = True" to automatically add a Vectorize wrapper.'.format(env))

        self.env = env
示例#15
0
    def _handle_crashed_n(self, info_n):
        # for i in self.crashed:
        #     info_n[i]['env_status.crashed'] = True

        if self.allow_reconnect:
            return

        if len(self.crashed) > 0:
            errors = {}
            for i, info in enumerate(info_n):
                if 'error' in info:
                    errors[self.crashed[i]] = info['error']

            if len(errors) == 0:
                raise error.Error(
                    '{}/{} environments have crashed. No error key in info_n: {}'
                    .format(len(self.crashed), self.n, info_n))
            else:
                raise error.Error(
                    '{}/{} environments have crashed! Most recent error: {}'.
                    format(len(self.crashed), self.n, errors))
示例#16
0
 def build(cls, metadata_encoding, pool, qr_pool, label):
     metadata_encoding = metadata_encoding.copy()
     type = metadata_encoding.pop('type')
     if type == 'qrcode':
         return QRCodeMetadataDecoder(label=label,
                                      pool=pool,
                                      qr_pool=qr_pool,
                                      **metadata_encoding)
     elif type == 'pixels':
         return PixelsMetadataDecoder(label=label)
     else:
         raise error.Error('Invalid encoding: {}'.format(type))
示例#17
0
    def __init__(self,
                 remotes,
                 error_buffer,
                 encoding=None,
                 compress_level=None,
                 fine_quality_level=None,
                 subsample_level=None):
        """compress_level: 0-9 [9 is highest compression]
        fine_quality_level: 0-100 [100 is best quality]
        subsample_level: 0-3 [0 is best quality]

        Lots of references for this, but
        https://github.com/TurboVNC/turbovnc/blob/master/doc/performance.txt
        is decent.
        """

        load_pygame()
        import libvncdriver

        if encoding is None:
            encoding = os.environ.get('LIBVNC_ENCODING', 'tight')
        if compress_level is None:
            compress_level = int(os.environ.get('LIBVNC_COMPRESS_LEVEL', '0'))
        if fine_quality_level is None:
            fine_quality_level = int(
                os.environ.get('LIBVNC_FINE_QUALITY_LEVEL', '100'))
        if subsample_level is None:
            subsample_level = int(os.environ.get('LIBVNC_SUBSAMPLE_LEVEL',
                                                 '0'))

        if not hasattr(libvncdriver, 'VNCSession'):
            raise error.Error('''
 *=================================================*
|| libvncdriver is not installed                   ||
|| Try installing with "pip install libvncdriver"  ||
|| or use the go or python driver by setting       ||
|| JIMINY_VNCDRIVER=go                             ||
|| JIMINY_VNCDRIVER=py                             ||
 *=================================================*''')
        logger.info("Using libvncdriver's %s encoding" % encoding)
        self.driver = libvncdriver.VNCSession(
            remotes=remotes,
            error_buffer=error_buffer,
            encoding=encoding,
            compress_level=compress_level,
            fine_quality_level=fine_quality_level,
            subsample_level=subsample_level,
        )
        self.screen = None
        self.render_called_once = False
        if PYGAME_INSTALLED:
            pygame.init()
示例#18
0
    def _spawn(self):
        if self.runtime.image is None:
            raise error.Error('No image specified')
        assert self._container_id is None

        self.vnc_port, self.rewarder_port, self._container_id = self.assigner.allocate_ports(
            2)
        if self._container_id is not None:
            logger.info('[%s] Reusing container %s on ports %s and %s',
                        self.label, self._container_id[:12], self.vnc_port,
                        self.rewarder_port)
            self.reusing = True
            self.started = True
            return

        self.reusing = False
        logger.info(
            '[%s] Creating container: image=%s. Run the same thing by hand as: %s',
            self.label, self.runtime.image,
            pretty_command(
                self.runtime.cli_command(self.vnc_port, self.rewarder_port)))
        try:
            container = self._spawn_container()
        except docker.errors.NotFound as e:
            # Looks like we need to pull the image
            assert 'No such image' in e.explanation.decode(
                'utf-8'
            ), 'Expected NotFound error message message to include "No such image", but it was: {}. This is probably just a bug in this assertion and the assumption was incorrect'.format(
                e.explanation)

            logger.info('Image %s not present locally; pulling',
                        self.runtime.image)
            self._pull_image()
            # If we called pull_image from multiple processes (as we do with jiminy-starter-agent A3C)
            # these will all return at the same time. We probably all got the same port numbers before the pull started,
            # so wait a short random time and refresh our port numbers
            time.sleep(random.uniform(0.5, 2.5))
            self.assigner._refresh_ports()
            self.vnc_port, self.rewarder_port, self._container_id = self.assigner.allocate_ports(
                2)
            if self._container_id is not None:
                logger.info('[%s] Reusing container %s on ports %s and %s',
                            self.label, self._container_id[:12], self.vnc_port,
                            self.rewarder_port)
                self.reusing = True
                self.started = True
                return
            # Try spawning again.
            container = self._spawn_container()

        self._container_id = container['Id']
示例#19
0
 def _step(self, action):
     try:
         for a, client in zip(action, self._clients):
             for event in a:
                 if event[0] == 'KeyEvent':
                     key, down = event[1:]
                     client.send_KeyEvent(key, down)
                 elif event[0] == 'PointerEvent':
                     x, y, buttomask = event[1:]
                     client.send_PointerEvent(x, y, buttomask)
                 else:
                     raise error.Error('Bad event type: {}'.format(type))
     except Exception as e:
         self.error_buffer.record(e)
示例#20
0
    def wait_for_step(self, error_buffer=None, timeout=None):
        # TODO: this might be cleaner using channels
        with self.cv:
            start = time.time()
            while True:
                if self.count != 0:
                    return
                elif timeout is not None and time.time() - start > timeout:
                    raise error.Error(
                        'No rewards received in {}s'.format(timeout))

                if error_buffer:
                    error_buffer.check()

                self.cv.wait(timeout=0.5)
示例#21
0
    def onConnect(self, request):
        if not os.path.exists('/usr/local/boxware/privileged_state/password'):
            raise error.Error(
                'No such file: /usr/local/boxware/privileged_state/password. (HINT: did the init script run /app/jiminy-envs/base/boxware-setpassword?)'
            )
        with open('/usr/local/boxware/privileged_state/password') as f:
            password = f.read().strip()

        self._message_id = 0
        self._request = request
        self._observer = request.headers.get('boxware-observer') == 'true'
        self.password = password

        logger.info('Client connecting: peer=%s observer=%s', request.peer,
                    self._observer)
示例#22
0
def blockingCallFromThread(f, *a, **kw):
    local_queue = queue.Queue()

    def _callFromThread():
        result = defer.maybeDeferred(f, *a, **kw)
        result.addBoth(local_queue.put)

    reactor.callFromThread(_callFromThread)
    result = queue_get(local_queue)
    if isinstance(result, failure.Failure):
        if result.frames:
            e = error.Error(str(result))
        else:
            e = result.value
        raise e
    return result
示例#23
0
    def _allocate(self, handles, initial, params):
        self._sleep = 1

        _params = self.params.copy()
        _params.update(params)

        for handle in handles:
            history = self._reconnect_history.get(handle, [])
            history.append(time.time())
            floor = time.time() - 5 * 60
            history = [entry for entry in history if entry > floor]
            if len(history) > 5:
                raise error.Error(
                    'Tried reallocating a fresh remote at index {} a total of {} times in the past 5 minutes (at {}). Please examine the logs to determine why the remotes keep failing.'
                    .format(handle, len(history), history))
            self._reconnect_history[handle] = history

        assert all(re.search('^\d+$', h) for h in
                   handles), "All handles must be numbers: {}".format(handles)
        allocation = self.with_retries(
            self._requestor.allocation_create,
            client_id=self.client_id,
            runtime_id=self.runtime_id,
            placement=self.placement,
            params=_params,
            handles=handles,
            initial=initial,
        )
        news = len(
            [entry for entry in allocation['info']['n'] if entry['new']])
        extra_logger.info(
            '[%s] Received allocation with %s new and %s existing envs: %s',
            self.label, news,
            len(allocation['info']['n']) - news, allocation)

        assert len(allocation['env_n']) <= len(
            handles
        ), "Received more envs than requested: allocation={} handles={}".format(
            allocation, handles)
        _, pending = self._handle_allocation(allocation)

        for env in pending:
            self.pending[env['name']] = {
                'handle': env['handle'],
                'params': params,
                'received_at': time.time()
            }
示例#24
0
    def build(cls, remotes, **kwargs):
        parsed = urlparse.urlparse(remotes)
        if parsed.scheme != 'vnc':
            raise error.Error(
                'HardcodedAddresses must be initialized with a string starting with vnc://: {}'
                .format(remotes))

        addresses = parsed.netloc.split(',')
        query = urlparse.parse_qs(parsed.query)
        # We could support per-backend passwords, but no need for it
        # right now.
        password = query.get('password', [utils.default_password()])[0]
        vnc_addresses, rewarder_addresses = parse_remotes(addresses)
        res = cls(vnc_addresses,
                  rewarder_addresses,
                  vnc_password=password,
                  rewarder_password=password,
                  **kwargs)
        return res, res.available_n
示例#25
0
    def run(self):
        target = time.time() + self.timeout
        while self.sockets:
            remaining = target - time.time()
            if remaining < 0:
                break
            ready, _, _ = select.select(self.sockets.keys(), [], [], remaining)

            # Go through the readable sockets
            remote_closed = False
            for sock in ready:
                type, address = self.sockets.pop(sock)

                # Connection was closed; try again.
                #
                # This is guaranteed not to block.
                try:
                    recv = sock.recv(1)
                except socket.error as e:
                    if e.errno == errno.ECONNRESET:
                        recv = b''
                    else:
                        raise

                if recv == b'':
                    logger.info('Remote closed: address=%s', address)
                    remote_closed = True
                    if type == 'rewarder':
                        self._register_rewarder(address)
                    else:
                        self._register_vnc(address)
                else:
                    logger.debug('Healthcheck passed for %s %s', type, address)

                sock.close()

            if remote_closed:
                sleep = 1
                logger.info('At least one sockets was closed by the remote. Sleeping %ds...', sleep)
                time.sleep(sleep)

        if self.sockets:
            raise error.Error('Not all servers came up within {}s: {}'.format(self.timeout, list(self.sockets.values())))
示例#26
0
 def _apply(self, framebuffer_update):
     if self.paint_cursor:
         self._unpaint_cursor()
     for rect in framebuffer_update.rectangles:
         if isinstance(
                 rect.encoding,
             (server_messages.RAWEncoding, server_messages.ZRLEEncoding,
              server_messages.ZlibEncoding)):
             self._update_rectangle(rect.x, rect.y, rect.width, rect.height,
                                    rect.encoding.data)
         elif isinstance(rect.encoding,
                         server_messages.PseudoCursorEncoding):
             self._update_cursor_shape(rect.x, rect.y, rect.width,
                                       rect.height, rect.encoding.image,
                                       rect.encoding.mask)
         else:
             raise error.Error('Unrecognized encoding: {}'.format(
                 rect.encoding))
     if self.paint_cursor:
         self._paint_cursor()
示例#27
0
def gym_core_action_space(gym_core_id):
    spec = gym.spec(gym_core_id)

    if spec.id == 'CartPole-v0':
        return spaces.Hardcoded([[spaces.KeyEvent.by_name('left', down=True)],
                                 [spaces.KeyEvent.by_name('left', down=False)]])
    elif spec._entry_point.startswith('gym.envs.atari:'):
        actions = []
        env = spec.make()
        for action in env.unwrapped.get_action_meanings():
            z = 'FIRE' in action
            left = 'LEFT' in action
            right = 'RIGHT' in action
            up = 'UP' in action
            down = 'DOWN' in action
            translated = atari_vnc(up=up, down=down, left=left, right=right, z=z)
            actions.append(translated)
        return spaces.Hardcoded(actions)
    else:
        raise error.Error('Unsupported env type: {}'.format(spec.id))
示例#28
0
    def from_remotes(cls, client_id, remotes, runtime_id, runtime_tag,
                     start_timeout, api_key, use_recorder_ports):
        parsed = urlparse.urlparse(remotes)
        if not (parsed.scheme == 'http' or parsed.scheme == 'https'):
            raise error.Error(
                'AllocatorManager must start with http:// or https://: {}'.
                format(remotes))

        base_url = parsed.scheme + '://' + parsed.netloc
        if parsed.path:
            base_url += '/' + parsed.path
        query = urlparse.parse_qs(parsed.query)

        # Intercept url-encoded params ("?n=2" and similar)
        params = {}
        n = query.get('n', [1])[0]  # not added to params, just returned later

        cpu = query.get('cpu', [None])[0]
        if cpu is not None:
            cpu = float(cpu)
            params['cpu'] = cpu

        tag = query.get('tag', [None])[0]
        if tag is not None:
            params[
                'tag'] = tag  # url-encoded "?tag=" gets precedence over runtimes.yml tag
        else:
            params['tag'] = runtime_tag

        placement = query.get('address', ['public'])[0]

        # anything else from the query other than the components processed above will get dropped on the floor

        return cls(client_id=client_id,
                   runtime_id=runtime_id,
                   base_url=base_url,
                   start_timeout=start_timeout,
                   params=params,
                   placement=placement,
                   api_key=api_key,
                   use_recorder_ports=use_recorder_ports), int(n)
示例#29
0
    def start(self, attempts=None):
        if attempts is None:
            # If we're reusing, we don't scan through ports for a free
            # one.
            if not self.assigner.reuse:
                attempts = 20
            else:
                attempts = 1

        for attempt in range(attempts):
            self._spawn()
            e = self._start()
            if e is None:
                return

            time.sleep(random.uniform(1.0, 5.0))
            self.assigner._refresh_ports()

        raise error.Error(
            '[{}] Could not start container after {} attempts. Last error: {}'.
            format(self.label, attempts, e))
示例#30
0
    def _compile_actions(self, action_n):
        compiled_n = []
        peek_d = {}
        try:
            for i, action in enumerate(action_n):
                compiled = []
                compiled_n.append(compiled)
                for event in action:
                    # Handle any special control actions
                    if event == spaces.PeekReward:
                        name = self.connection_names[i]
                        peek_d[name] = True
                        continue

                    # Do a generic compile
                    compiled.append(compile_action(event))
        except Exception as e:
            raise error.Error(
                'Could not compile actions. Original error: {} ({}). action_n={}'
                .format(e, type(e), action_n))
        else:
            return compiled_n, peek_d