Ejemplo n.º 1
0
 async def connect(
     self,
     on_close: Callable[['_Connection', Exception], Awaitable[None]],
 ) -> None:
     with wrap2span(
             name=AmqpSpan.NAME_CONNECT,
             kind=AmqpSpan.KIND_CLIENT,
             cls=AmqpSpan,
             app=self.pika.app,
     ) as span:
         span.tag(AmqpSpan.TAG_URL, self.pika._masked_url)
         async with self._lock:
             self._state = self.STATE_CONNECTING
             self._on_close = on_close
             self._fut = asyncio.Future()
             self._conn = AsyncioConnection(
                 parameters=pika.URLParameters(self.cfg.url),
                 on_open_callback=self._on_connection_open,
                 on_open_error_callback=self._on_connection_open_error,
                 on_close_callback=self._on_connection_closed,
             )
             try:
                 await asyncio.wait_for(self._fut,
                                        timeout=self.cfg.connect_timeout)
                 if self._fut.result() != 1:
                     raise RuntimeError()
                 self._state = self.STATE_CONNECTED
             except Exception:
                 self._state = self.STATE_CLOSED
                 raise
             finally:
                 self._fut = None
Ejemplo n.º 2
0
    def connect(self):
        """
        This method connects to RabbitMQ, returning the connection handle.
        When the connection is established, the on_connection_open method
        will be invoked by pika.
        ------------------
        :rtype: pika.SelectConnection

        """
        if self.async_processing:
            asyncio.set_event_loop(asyncio.new_event_loop())
            return AsyncioConnection(
                parameters=pika.URLParameters("{}{}".format(
                    self._url, self.heartbeat)),
                on_open_callback=self.on_connection_open,
                on_open_error_callback=self.on_open_error_callback,
                # on_close_callback=self.on_connection_closed
            )
        else:
            self._connection = pika.BlockingConnection(
                parameters=pika.URLParameters("{}{}".format(
                    self._url, self.heartbeat)),
                # on_open_callback=self.on_connection_open,
                # on_open_error_callback=self.on_open_error_callback,
                # on_close_callback=self.on_connection_closed
            )
            self.on_connection_open(self._connection)
            return self._connection
Ejemplo n.º 3
0
 def connect(self):
     """This method connects to RabbitMQ, returning the connection handle.
     When the connection is established, the on_connection_open method
     will be invoked by pika.
     :rtype: pika.adapters.asyncio_connection.AsyncioConnection
     """
     LOGGER.info('Connecting to %s', self._url)
     return AsyncioConnection(
         parameters=pika.URLParameters(self._url),
         on_open_callback=self.on_connection_open,
         on_open_error_callback=self.on_connection_open_error,
         on_close_callback=self.on_connection_closed)
async def listen_to_rabbitmq(queue_name):
    loop = asyncio.get_running_loop()

    fut = loop.create_future()
    AsyncioConnection(
        pika.ConnectionParameters('localhost'),
        on_open_callback=lambda c: fut.set_result(c),
        on_open_error_callback=lambda c, exc: fut.set_exception(exc),
        on_close_callback=lambda c, exc: fut.set_exception(exc))
    conn = await fut
    fut = loop.create_future()
    conn.channel(on_open_callback=lambda ch: fut.set_result(ch))
    channel = await fut

    fut = loop.create_future()
    channel.queue_declare(queue=queue_name,
                          durable=True,
                          auto_delete=True,
                          arguments={'x-expires': 86400000},
                          callback=lambda ch: fut.set_result(ch))
    cnt = await fut

    if cnt:
        messages = asyncio.Queue()
        channel.basic_consume(
            queue_name,
            on_message_callback=lambda *args: messages.put_nowait(args[3]),
            auto_ack=True)

        fut = loop.create_future()

        def on_close_callback(ch, reason):
            try:
                raise reason
            except ChannelClosedByClient:
                fut.cancel()
            except:
                fut.set_exception(reason)

        channel.add_on_close_callback(on_close_callback)

        while True:
            msg = asyncio.ensure_future(messages.get())
            await asyncio.wait([msg, fut], return_when=asyncio.FIRST_COMPLETED)
            if fut.done():
                if fut.cancelled():
                    break
                raise fut.exception()
            decoded_message = msg.result().decode('utf-8').replace(
                'null', 'None')
            decoded_message_dict = ast.literal_eval(decoded_message)
            return decoded_message_dict['result']
Ejemplo n.º 5
0
 def connect(self):
     LOGGER.info('Connecting to %s', self._url)
     url = urlparse(self._url)
     creds = pika.PlainCredentials(url.username, url.password)
     parameters = pika.ConnectionParameters(
         host=url.hostname,
         port=url.port,
         virtual_host=url.path,
         credentials=creds
     )
     return AsyncioConnection(
         parameters=parameters,
         on_open_callback=self.on_connection_open,
         on_open_error_callback=self.on_connection_open_error,
         on_close_callback=self.on_connection_closed)
Ejemplo n.º 6
0
    def connect(self):
        """This method connects to RabbitMQ, returning the connection handle.
        When the connection is established, the on_connection_open method
        will be invoked by pika.


        Returns
        -------
        AsyncioConnection

        """
        LOGGER.info('Connecting...')
        return AsyncioConnection(
            parameters=pika.URLParameters(self._url),
            on_open_callback=self.on_connection_open,
            on_open_error_callback=self.on_connection_open_error)
Ejemplo n.º 7
0
    def try_connect(self):
        self.logger.info("Creating connection to RabbitMQ")
        self._io_loop.call_later(self._timeout, self._on_timeout)

        if isinstance(self._io_loop, IOLoop):
            TornadoConnection(self._parameters,
                              on_open_callback=self._open_callback,
                              on_open_error_callback=self._open_error_callback,
                              on_close_callback=self._close_callback,
                              custom_ioloop=self._io_loop)
        else:
            AsyncioConnection(self._parameters,
                              on_open_callback=self._open_callback,
                              on_open_error_callback=self._open_error_callback,
                              on_close_callback=self._close_callback,
                              custom_ioloop=self._io_loop)
Ejemplo n.º 8
0
 def connect(self):
     self._connection = AsyncioConnection(
         pika.URLParameters(self._url),
         on_open_callback=self.on_connection_open,
         on_close_callback=self.on_connection_closed,
         on_open_error_callback=self.on_connection_open_error)
Ejemplo n.º 9
0
class Publisher(object):
    """RabbitMQ publisher

    Parameters
    ----------
    amqp_url : `str`
        Broker url
    csc_parent : `lsst.dm.csc.base.dm_csc`
        CSC service using this publisher
    logger_level : `int`
        log level

    """
    def __init__(self, amqp_url, csc_parent=None, logger_level=LOGGER.info):

        # only emit logging messages from pika and WARNING and above
        logging.getLogger("pika").setLevel(logging.WARNING)
        self._connection = None
        self._channel = None
        self._url = amqp_url
        self.setup_complete_event = asyncio.Event()
        self.setup_complete_event.clear()

        self._message_handler = YamlHandler()
        self._stopping = False

        self.csc_parent = csc_parent
        self.logger_level = logger_level

    def connect(self):
        self._connection = AsyncioConnection(
            pika.URLParameters(self._url),
            on_open_callback=self.on_connection_open,
            on_close_callback=self.on_connection_closed,
            on_open_error_callback=self.on_connection_open_error)

    def on_connection_open(self, connection):
        LOGGER.info("connection opened")
        self._connection = connection
        self.open_channel()

    def on_connection_open_error(self, _unused_connection, err):
        """This method is called by pika if the connection to RabbitMQ
        can't be established.

        :param pika.adapters.asyncio_connection.AsyncioConnection _unused_connection:
           The connection
        :param Exception err: The error

        """
        LOGGER.error(f'Connection open failed: {err}')
        if self.csc_parent is not None:
            self.csc_parent.fault(5071, f'Connection open failed: {err}')

    def open_channel(self):
        LOGGER.info("creating a new channel")
        self._connection.channel(on_open_callback=self.on_channel_open)

    def on_channel_open(self, channel):
        LOGGER.info("channel opened")
        self._channel = channel
        self.add_on_channel_close_callback()
        self.setup_complete_event.set()

    def add_on_channel_close_callback(self):
        LOGGER.info('adding channel close callback')
        self._channel.add_on_close_callback(self.on_channel_closed)

    def on_channel_closed(self, channel, reason):
        LOGGER.info('Channel %i was closed %s' % (channel, reason))
        self._channel = None
        self._connection.close()

    def on_connection_closed(self, connection, reason):
        """
        Params:
            connection - closed object connection
            reason - reason for closure
        """
        LOGGER.info("on_connection_closed called")
        self._channel = None
        self._connect = None

    async def close(self):
        if self._channel is not None:
            self._channel.close()

    async def publish_message(self, route_key, msg):

        encoded_data = self._message_handler.encode_message(msg)

        self.logger_level("Sending msg to %s", route_key)

        # Since this is asynchronous, it's possible to still be in the
        # process of getting setup and having self._channel be None when
        # publish_message is being called from another thread, so we wait
        # here until setup is completed.
        await self.setup_complete_event.wait()
        self._channel.basic_publish(exchange='message',
                                    routing_key=route_key,
                                    body=encoded_data)
        self.logger_level(f'message sent message body is: {msg}')

    async def stop(self):
        self._stopping = True
        await self.close()
        LOGGER.info('Stopped')

    async def start(self):
        try:
            await self.connect()
        except Exception:
            LOGGER.error('No channel - connection channel is None')
Ejemplo n.º 10
0
class _Connection:
    STATE_NONE = 0
    STATE_CONNECTING = 1
    STATE_CONNECTED = 2
    STATE_CLOSING = 3
    STATE_CLOSED = 4

    def __init__(
        self,
        pika: 'Pika',
        cfg: PikaConfig,
        *,
        loop: Optional[asyncio.AbstractEventLoop] = None,
    ) -> None:
        self.pika = pika
        self.cfg = cfg
        self._conn: Optional[AsyncioConnection] = None
        self._loop: asyncio.AbstractEventLoop = (loop
                                                 or asyncio.get_event_loop())
        self._fut: Optional[asyncio.Future] = None
        self._lock = asyncio.Lock()
        self._on_close: Optional[Callable[['_Connection', Exception],
                                          Awaitable[None]]] = None
        self._state = self.STATE_NONE

    @property
    def state(self) -> int:
        return self._state

    async def connect(
        self,
        on_close: Callable[['_Connection', Exception], Awaitable[None]],
    ) -> None:
        with wrap2span(
                name=AmqpSpan.NAME_CONNECT,
                kind=AmqpSpan.KIND_CLIENT,
                cls=AmqpSpan,
                app=self.pika.app,
        ) as span:
            span.tag(AmqpSpan.TAG_URL, self.pika._masked_url)
            async with self._lock:
                self._state = self.STATE_CONNECTING
                self._on_close = on_close
                self._fut = asyncio.Future()
                self._conn = AsyncioConnection(
                    parameters=pika.URLParameters(self.cfg.url),
                    on_open_callback=self._on_connection_open,
                    on_open_error_callback=self._on_connection_open_error,
                    on_close_callback=self._on_connection_closed,
                )
                try:
                    await asyncio.wait_for(self._fut,
                                           timeout=self.cfg.connect_timeout)
                    if self._fut.result() != 1:
                        raise RuntimeError()
                    self._state = self.STATE_CONNECTED
                except Exception:
                    self._state = self.STATE_CLOSED
                    raise
                finally:
                    self._fut = None

    async def close(self) -> None:
        async with self._lock:
            if self._conn is None:
                self._state = self.STATE_CLOSED
                return

            self._state = self.STATE_CLOSING
            self._on_close = None
            self._fut = asyncio.Future()
            self._conn.close()
            try:
                await asyncio.wait_for(self._fut,
                                       timeout=self.cfg.connect_timeout)
            except pika.exceptions.ConnectionClosed:
                self._state = self.STATE_CLOSED
                return
            finally:
                self._fut = None

    def _on_connection_open(self,
                            _unused_connection: AsyncioConnection) -> None:
        if self._fut is not None:
            self._fut.set_result(1)

    def _on_connection_open_error(self, _unused_connection: AsyncioConnection,
                                  err: Exception) -> None:
        if self._fut is not None:
            self._fut.set_exception(err)
        elif self._on_close is not None:
            asyncio.ensure_future(self._on_close(self, err))

    def _on_connection_closed(self, _unused_connection: AsyncioConnection,
                              reason: Exception) -> None:
        if self._fut is not None:
            self._fut.set_exception(reason)
        elif self._on_close is not None:
            asyncio.ensure_future(self._on_close(self, reason))

    async def channel(
        self,
        on_close: Optional[Callable[[pika.channel.Channel, Exception],
                                    Awaitable[None]]],
        name: Optional[str],
    ) -> pika.channel.Channel:
        with wrap2span(
                name=AmqpSpan.NAME_CHANNEL,
                kind=AmqpSpan.KIND_CLIENT,
                cls=AmqpSpan,
                app=self.pika.app,
        ) as span:
            if name is not None:
                span.tag(AmqpSpan.TAG_CHANNEL_NAME, name)
            async with self._lock:
                if self._conn is None:
                    raise pika.exceptions.AMQPConnectionError()
                self._fut = asyncio.Future()
                self._conn.channel(
                    on_open_callback=partial(self._on_channel_open, on_close))
                try:
                    channel: Any = await asyncio.wait_for(
                        self._fut, timeout=self.cfg.channel_open_timeout)
                    if not isinstance(channel, pika.channel.Channel):
                        raise RuntimeError()
                finally:
                    self._fut = None

                span.tag(AmqpSpan.TAG_CHANNEL_NUMBER,
                         str(channel.channel_number))

                return channel

    def _on_channel_open(
        self,
        on_close: Optional[Callable[[pika.channel.Channel, Exception],
                                    Awaitable[None]]],
        channel: pika.channel.Channel,
    ) -> None:
        channel.add_on_close_callback(
            partial(self._on_channel_closed, on_close))
        if self._fut is None:
            raise RuntimeError()
        self._fut.set_result(channel)

    def _on_channel_closed(
        self,
        on_close: Optional[Callable[[pika.channel.Channel, Exception],
                                    Awaitable[None]]],
        channel: pika.channel.Channel,
        reason: Exception,
    ) -> None:
        if on_close is not None:
            asyncio.ensure_future(on_close(channel, reason))
Ejemplo n.º 11
0
 def _connect(self) -> None:
     self._conn = AsyncioConnection(
         parameters=get_mq_conn_params(),
         on_open_callback=self._conn_on_open,
         on_open_error_callback=self._conn_on_open_error,
         on_close_callback=self._conn_on_close)
Ejemplo n.º 12
0
class Worker(object):
    def __init__(self, db_config: dict, max_processes: int) -> None:
        self._max_processes = max_processes
        self._executor = ProcessPoolExecutor(
            max_workers=max_processes,
            mp_context=mp.get_context('spawn'),
            initializer=partial(_initializer, db_config))
        self._queue_name = 'judge_queue'
        self._conn: AsyncioConnection = None
        self._ch: Channel = None
        self._hostname: Optional[str] = None
        self._pid = os.getpid()
        self._task_processed, self._task_errors = 0, 0
        self._maint_interval = timedelta(seconds=60)

    def __enter__(self) -> 'Worker':
        return self

    def __exit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> None:
        self._executor.shutdown(wait=False)
        if self._ch:
            self._ch.close()
        if self._conn:
            self._conn.close()

    def start(self) -> None:
        self._connect()
        asyncio.get_event_loop().call_soon_threadsafe(self._update_status)
        asyncio.get_event_loop().run_forever()

    def _connect(self) -> None:
        self._conn = AsyncioConnection(
            parameters=get_mq_conn_params(),
            on_open_callback=self._conn_on_open,
            on_open_error_callback=self._conn_on_open_error,
            on_close_callback=self._conn_on_close)

    def _schedule_update_status(self) -> None:
        asyncio.get_event_loop().call_later(
            uniform(
                self._maint_interval.total_seconds() - 1,
                self._maint_interval.total_seconds() + 1,
            ), self._update_status)

    def _update_status(self) -> None:
        updates = dict(
            last_contact=func.now(),
            processed=self._task_processed,
            errors=self._task_errors,
        )
        try:
            hostname = self._hostname or gethostname()
            with transaction() as s:
                if self._hostname:
                    s.query(WorkerTable).filter(
                        WorkerTable.hostname == self._hostname,
                        WorkerTable.pid == self._pid).update(
                            updates, synchronize_session=False)
                else:
                    updates.update(
                        dict(hostname=hostname,
                             pid=self._pid,
                             max_processes=self._max_processes,
                             startup_time=func.now()))
                    s.add(WorkerTable(**updates))
                if uniform(0, 1) <= 0.01 or not self._hostname:
                    threshold = self._maint_interval * 10
                    s.query(WorkerTable).filter(
                        func.now() -
                        WorkerTable.last_contact > threshold).delete(
                            synchronize_session=False)
            self._hostname = hostname
        except Exception:
            pass
        self._schedule_update_status()

    def _conn_on_open(self, _: AsyncioConnection) -> None:
        self._conn.channel(on_open_callback=self._ch_on_open)

    def _conn_on_open_error(self, _: AsyncioConnection,
                            err: AMQPError) -> None:
        LOGGER.warning(
            'Cannot open RabbitMQ connection({}). retrying...'.format(err))
        asyncio.get_event_loop().call_later(uniform(1, 5), self._connect)

    def _conn_on_close(self, _: AsyncioConnection, reason: AMQPError) -> None:
        LOGGER.warning(
            'RabbitMQ connection closed({}). retrying...'.format(reason))
        asyncio.get_event_loop().call_later(uniform(1, 5), self._connect)

    def _ch_on_open(self, ch: Channel) -> None:
        self._ch = ch
        ch.add_on_close_callback(self._ch_on_close)
        ch.queue_declare(queue=self._queue_name,
                         callback=self._on_queue_declared)

    def _ch_on_close(self, ch: Channel, reason: AMQPError) -> None:
        LOGGER.warning('RabbitMQ channel closed ({})'.format(reason))
        try:
            self._conn.close()
        except Exception:
            pass

    def _on_queue_declared(self, method: pika.frame.Method) -> None:
        self._ch.basic_qos(prefetch_count=self._max_processes,
                           callback=self._on_basic_qos_ok)

    def _on_basic_qos_ok(self, method: pika.frame.Method) -> None:
        self._ch.basic_consume(self._queue_name,
                               on_message_callback=self._recv_message)
        LOGGER.info('Worker started')

    def _recv_message(self, ch: Channel, method: pika.spec.Basic.Return,
                      _: pika.spec.BasicProperties, body: bytes) -> None:
        try:
            self._process(ch, method, body)
        except Exception:
            LOGGER.warning('', exc_info=True)

    def _process(self, ch: Channel, method: pika.spec.Basic.Return,
                 body: bytes) -> None:
        def _done(fut: Optional[Future]) -> None:
            ch.basic_ack(delivery_tag=method.delivery_tag)
            if fut is None:
                return
            self._task_processed += 1
            if fut.exception() is not None:
                self._task_errors += 1
            elif fut.result() == JudgeStatus.InternalError:
                self._task_errors += 1

        try:
            contest_id, problem_id, submission_id = pickle.loads(body)
        except Exception:
            LOGGER.warning('Received invalid message. ignored.', exc_info=True)
            _done(None)
            return

        with transaction() as s:
            submission = s.query(Submission).with_for_update().filter(
                Submission.contest_id == contest_id,
                Submission.problem_id == problem_id,
                Submission.id == submission_id).first()
            if not submission:
                LOGGER.warning(
                    'Submission.id "{}" is not found'.format(submission_id))
                _done(None)
            if submission.status not in (JudgeStatus.Waiting,
                                         JudgeStatus.Running,
                                         JudgeStatus.InternalError):
                _done(None)
            env = s.query(Environment).filter(
                Environment.id == submission.environment_id).first()
            problem = s.query(Problem).filter(
                Problem.contest_id == contest_id,
                Problem.id == problem_id).first()
            assert env and problem  # env/problemは外部キー制約によって常に取得可能

            # ワーカーダウン等ですべてのテストのジャッジが完了していない場合は
            # ジャッジ済みのテストは結果を流用する
            existed_results = {
                jr.test_id: jr
                for jr in s.query(JudgeResult).filter(
                    JudgeResult.contest_id == contest_id,
                    JudgeResult.problem_id == problem_id,
                    JudgeResult.submission_id == submission_id)
            }

            task = JudgeTask(id=submission_id,
                             contest_id=contest_id,
                             problem_id=problem_id,
                             user_id=submission.user_id,
                             code=submission.code,
                             compile_image_name=env.compile_image_name,
                             test_image_name=env.test_image_name,
                             time_limit=problem.time_limit,
                             memory_limit=problem.memory_limit,
                             tests=[])
            submission.status = JudgeStatus.Running
            testcases = s.query(TestCase).filter(
                TestCase.contest_id == contest_id,
                TestCase.problem_id == problem_id).all()
            for test in testcases:
                jr = existed_results.get(test.id, None)
                if not jr:
                    s.add(
                        JudgeResult(contest_id=contest_id,
                                    problem_id=problem_id,
                                    submission_id=submission_id,
                                    test_id=test.id))
                if not jr or jr.status in (JudgeStatus.Waiting,
                                           JudgeStatus.Running,
                                           JudgeStatus.InternalError):
                    task.tests.append(
                        JudgeTestInfo(id=test.id,
                                      input=test.input,
                                      output=test.output))

        # テストの実行順序をシャッフルする
        shuffle(task.tests)

        def _submit() -> None:
            LOGGER.info('submit to child process (submission.id={})'.format(
                submission_id))
            future = self._executor.submit(run, DockerJudgeDriver, task)
            future.add_done_callback(_done)

        asyncio.get_event_loop().call_soon_threadsafe(_submit)
Ejemplo n.º 13
0
 def connect(self):
     return AsyncioConnection(
         on_open_callback=self.on_connect_open,
     )
Ejemplo n.º 14
0
 def create_connection(self, parameters):
     LOGGER.info('Creating a new connection')
     return AsyncioConnection(parameters=parameters,
                              on_open_callback=self.on_connection_opened,
                              on_open_error_callback=self.on_connection_open_error,
                              on_close_callback=self.on_connection_closed)