Решение Tornado 4.x для запуска игры на ThreadPoolExecutor больше не работает. Нужна помощь в ее рефакторинге

#python #tornado

#python #tornado

Вопрос:

Мое решение ThreadPoolExecutor / gen.coroutine (tornado v4.x) для обхода блокировки веб-сервера больше не работает с tornado версии 6.x .

Некоторое время назад я начал разрабатывать браузерную онлайн-игру, используя веб-сервер Tornado (v4.x) и websockets. Всякий раз, когда ожидается ввод данных пользователем, игра отправляет вопрос клиенту и ожидает ответа. Раньше я использовал gen.coroutine и ThreadPoolExecutor, чтобы сделать эту задачу неблокирующей. Теперь, когда я начал рефакторинг игры, она не работает с tornado v6.x, и задача снова блокирует сервер. Я искал возможные решения, но пока мне не удалось заставить ее снова работать. Мне непонятно, как изменить мой существующий код, чтобы он снова не блокировался.

 server.py:

class PlayerWebSocket(tornado.websocket.WebSocketHandler):
    executor = ThreadPoolExecutor(max_workers=15)
    @run_on_executor
    def on_message(self,message):
        params = message.split(':')
        self.player.callbacks[int(params[0])]=params[1]

if __name__ == '__main__':
    application = Application()
    application.listen(9999)
    tornado.ioloop.IOLoop.instance().start()

  

player.py:

 @gen.coroutine
def send(self, message):
   self.socket.write_message(message)

def create_choice(self, id, choices):
    d = {}
    d['id'] = id
    d['choices']=choices
    self.choice[d['id']]=d
    self.send('update',self)
    while not d['id'] in self.callbacks:
        pass
    del self.choice[d['id']]
    return self.callbacks[d['id']]
  

Всякий раз, когда нужно сделать выбор, функция create_choice создает dict со списком (вариантами) и идентификатором и сохраняет его в players self.callbacks. После этого она просто остается в цикле while, пока функция websocket.on_message не поместит полученный ответ (который выглядит так: id: Choice_id, например, 1:12838732) в dict обратного вызова.

Ответ №1:

WebSocketHandler.write_message Метод не является потокобезопасным, поэтому его можно вызвать только из потока IOLoop, а не из ThreadPoolExecutor (это всегда было правдой, но иногда могло показаться, что он все равно работает).

Самый простой способ исправить этот код — сохранить IOLoop.current() в глобальной переменной из основного потока ( current() функция обращается к локальной переменной потока, поэтому вы не можете вызвать ее из пула потоков) и использовать ioloop.add_callback(self.socket.write_message, message) (и удалить @gen.coroutine из send — бесполезно создавать сопрограммы функций, если они не содержат yield выражений).